diff --git a/CHANGELOG.md b/CHANGELOG.md index 7408e0b3d0..d43b663681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,10 @@ Adding a new version? You'll need three changes: [#3832](https://github.com/Kong/kubernetes-ingress-controller/pull/3832) - Added license agent for Konnect-managed instances. [#3883](https://github.com/Kong/kubernetes-ingress-controller/pull/3883) +- `Service`, `Route` and `Consumer` Kong entities now get assigned deterministic + IDs based on their unique properties (name, username, etc.) instead of random + UUIDs. + [#3933](https://github.com/Kong/kubernetes-ingress-controller/pull/3933) ### Fixed diff --git a/go.mod b/go.mod index bb1020600d..04fb0d42de 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/uuid v1.3.0 github.com/kong/deck v1.20.0 - github.com/kong/go-kong v0.40.0 + github.com/kong/go-kong v0.41.0 github.com/kong/kubernetes-telemetry v0.0.2 github.com/kong/kubernetes-testing-framework v0.30.1 github.com/lithammer/dedent v1.1.0 @@ -114,7 +114,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect - github.com/huandu/xstrings v1.3.3 // indirect + github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/go.sum b/go.sum index f94d211f63..1308065111 100644 --- a/go.sum +++ b/go.sum @@ -469,6 +469,7 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -512,6 +513,8 @@ github.com/kong/deck v1.20.0 h1:q8+8VBnvv0O+9mYjcPdJP5prG3KzbvR4XfePwkTx+Zc= github.com/kong/deck v1.20.0/go.mod h1:yJWEu6/xnYiaNBg2vP4EsYLtbt33J67Zsolye3JpJmI= github.com/kong/go-kong v0.40.0 h1:6rd70L4GbPz90j3ey+wjHd4aC21uFgbqsoASJhbcbdU= github.com/kong/go-kong v0.40.0/go.mod h1:t3siZEEGBB3FA5EQv9CL5EcaiogPTG0A175VQ6KvEHE= +github.com/kong/go-kong v0.41.0 h1:0rn+Haf8wfT0VWFVjQPNETLju5ZuNhfbrHYyjpliDBU= +github.com/kong/go-kong v0.41.0/go.mod h1:S/Mx/ZjgwsREPcpMXgCFt5wX7LBpyFlTKENri7E3KTg= github.com/kong/kubernetes-telemetry v0.0.2 h1:ZLoctQzvo0onCxbMgFMGsIGu6qAXWaMrd4o5Rv//C68= github.com/kong/kubernetes-telemetry v0.0.2/go.mod h1:lOeCASSR93hssoiOI2HUHoMFLffo/4lLsk74pIqMcyo= github.com/kong/kubernetes-testing-framework v0.30.1 h1:FZCThCgf2xOi/pUbSzd5hW1ghUnZYihmvy9a3DHMRAE= diff --git a/internal/dataplane/kongstate/kongstate.go b/internal/dataplane/kongstate/kongstate.go index 168f54d195..d1dc87a3e6 100644 --- a/internal/dataplane/kongstate/kongstate.go +++ b/internal/dataplane/kongstate/kongstate.go @@ -356,3 +356,26 @@ func globalPlugins(log logrus.FieldLogger, s store.Storer) ([]Plugin, error) { func (ks *KongState) FillPlugins(log logrus.FieldLogger, s store.Storer) { ks.Plugins = buildPlugins(log, s, ks.getPluginRelations()) } + +func (ks *KongState) FillIDs(logger logrus.FieldLogger) { + for svcIndex, svc := range ks.Services { + if err := svc.FillID(); err != nil { + logger.WithError(err).Errorf("failed to fill ID for service %s", *svc.Name) + } + ks.Services[svcIndex] = svc + + for routeIndex, route := range svc.Routes { + if err := route.FillID(); err != nil { + logger.WithError(err).Errorf("failed to fill ID for route %s", *route.Name) + } + ks.Services[svcIndex].Routes[routeIndex] = route + } + } + + for consumerIndex, consumer := range ks.Consumers { + if err := consumer.FillID(); err != nil { + logger.WithError(err).Errorf("failed to fill ID for consumer %s", *consumer.Username) + } + ks.Consumers[consumerIndex] = consumer + } +} diff --git a/internal/dataplane/kongstate/kongstate_test.go b/internal/dataplane/kongstate/kongstate_test.go index f501958be5..e23dacde1c 100644 --- a/internal/dataplane/kongstate/kongstate_test.go +++ b/internal/dataplane/kongstate/kongstate_test.go @@ -8,6 +8,7 @@ import ( "github.com/kong/go-kong/kong" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -396,3 +397,121 @@ func TestFillConsumersAndCredentials(t *testing.T) { assert.Equal(t, want.Consumers[0].Oauth2Creds[0].RedirectURIs, state.Consumers[0].Oauth2Creds[0].RedirectURIs) }) } + +func TestKongState_FillIDs(t *testing.T) { + testCases := []struct { + name string + state KongState + expect func(t *testing.T, s KongState) + }{ + { + name: "fills service IDs", + state: KongState{ + Services: []Service{ + { + Service: kong.Service{ + Name: kong.String("service.foo"), + }, + }, + { + Service: kong.Service{ + Name: kong.String("service.bar"), + }, + }, + }, + }, + expect: func(t *testing.T, s KongState) { + require.NotEmpty(t, s.Services[0].ID) + require.NotEmpty(t, s.Services[1].ID) + }, + }, + { + name: "fills route IDs", + state: KongState{ + Services: []Service{ + { + Service: kong.Service{ + Name: kong.String("service.foo"), + }, + Routes: []Route{ + { + Route: kong.Route{ + Name: kong.String("route.foo"), + }, + }, + { + Route: kong.Route{ + Name: kong.String("route.bar"), + }, + }, + }, + }, + }, + }, + expect: func(t *testing.T, s KongState) { + require.NotEmpty(t, s.Services[0].ID) + require.NotEmpty(t, s.Services[0].Routes[0].ID) + require.NotEmpty(t, s.Services[0].Routes[1].ID) + }, + }, + { + name: "fills consumer IDs", + state: KongState{ + Consumers: []Consumer{ + { + Consumer: kong.Consumer{ + Username: kong.String("user.foo"), + }, + }, + { + Consumer: kong.Consumer{ + Username: kong.String("user.bar"), + }, + }, + }, + }, + expect: func(t *testing.T, s KongState) { + require.NotEmpty(t, s.Consumers[0].ID) + require.NotEmpty(t, s.Consumers[1].ID) + }, + }, + { + name: "fills services, routes, and consumer IDs", + state: KongState{ + Services: []Service{ + { + Service: kong.Service{ + Name: kong.String("service.foo"), + }, + Routes: []Route{ + { + Route: kong.Route{ + Name: kong.String("route.bar"), + }, + }, + }, + }, + }, + Consumers: []Consumer{ + { + Consumer: kong.Consumer{ + Username: kong.String("user.baz"), + }, + }, + }, + }, + expect: func(t *testing.T, s KongState) { + require.NotEmpty(t, s.Services[0].ID) + require.NotEmpty(t, s.Services[0].Routes[0].ID) + require.NotEmpty(t, s.Consumers[0].ID) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tc.state.FillIDs(logrus.New()) + tc.expect(t, tc.state) + }) + } +} diff --git a/internal/dataplane/parser/parser.go b/internal/dataplane/parser/parser.go index 5c86af9930..419492c2f8 100644 --- a/internal/dataplane/parser/parser.go +++ b/internal/dataplane/parser/parser.go @@ -135,6 +135,9 @@ func (p *Parser) Build() (*kongstate.KongState, []failures.ResourceFailure) { result.Licenses = append(result.Licenses, *p.license) } + // generate IDs for Kong entities + result.FillIDs(p.logger) + return &result, p.popTranslationFailures() } diff --git a/internal/dataplane/parser/parser_test.go b/internal/dataplane/parser/parser_test.go index 75cc7750f2..9553db12a0 100644 --- a/internal/dataplane/parser/parser_test.go +++ b/internal/dataplane/parser/parser_test.go @@ -4928,6 +4928,92 @@ func TestCertificate(t *testing.T) { }) } +func TestParser_FillsEntitiesIDs(t *testing.T) { + s, err := store.NewFakeStore(store.FakeObjects{ + Services: []*corev1.Service{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "svc.foo", + Namespace: "ns", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "foo", + Port: 80, + }, + }, + }, + }, + }, + IngressesV1: []*netv1.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress.foo", + Namespace: "ns", + Annotations: map[string]string{ + annotations.IngressClassKey: annotations.DefaultIngressClass, + }, + }, + Spec: netv1.IngressSpec{ + Rules: []netv1.IngressRule{ + { + Host: "foo.com", + IngressRuleValue: netv1.IngressRuleValue{ + HTTP: &netv1.HTTPIngressRuleValue{ + Paths: []netv1.HTTPIngressPath{ + { + Path: "/foo", + Backend: netv1.IngressBackend{ + Service: &netv1.IngressServiceBackend{ + Name: "svc.foo", + Port: netv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + KongConsumers: []*configurationv1.KongConsumer{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "user.foo", + Namespace: "ns", + Annotations: map[string]string{ + annotations.IngressClassKey: annotations.DefaultIngressClass, + }, + }, + Username: "user.foo", + }, + }, + }) + require.NoError(t, err) + p := mustNewParser(t, s) + + state, translationFailures := p.Build() + require.Empty(t, translationFailures) + require.NotNil(t, state) + + require.Len(t, state.Services, 1) + require.NotNil(t, state.Services[0].ID) + assert.Equal(t, "10157742-edd5-51f6-8141-f21dc8017e87", *state.Services[0].ID, "expected deterministic ID") + + require.Len(t, state.Services[0].Routes, 1) + require.NotNil(t, state.Services[0].Routes[0].ID) + assert.Equal(t, "1474bf56-c263-5919-b3e6-e9bc6e4b237f", *state.Services[0].Routes[0].ID, "expected deterministic ID") + + require.Len(t, state.Consumers, 1) + require.NotNil(t, state.Consumers[0].ID) + assert.Equal(t, "93c4b796-7cc1-5f86-834c-3bbdf00a806c", *state.Consumers[0].ID, "expected deterministic ID") +} + func mustNewParser(t *testing.T, storer store.Storer) *Parser { p, err := NewParser(logrus.New(), storer) require.NoError(t, err)