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..e68e7dfc5c 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,7 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= @@ -281,8 +282,8 @@ github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v2.20.0+incompatible h1:4Xh3bDzO29j4TWNOI+24ubc0vbVFMg2PMnXKxK54/CA= github.com/gobuffalo/flect v0.2.4/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -467,8 +468,9 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 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 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +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= @@ -510,8 +512,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 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= @@ -689,7 +691,7 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/internal/dataplane/kongstate/kongstate.go b/internal/dataplane/kongstate/kongstate.go index 168f54d195..d903970c09 100644 --- a/internal/dataplane/kongstate/kongstate.go +++ b/internal/dataplane/kongstate/kongstate.go @@ -356,3 +356,30 @@ 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()) } + +// FillIDs iterates over the KongState and fills in the ID field for each entity +// that supports the FillID method (these are Service, Route and Consumer). It makes +// their IDs deterministic, enabling their correct identification in external systems +// (e.g. Konnect Analytics). +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)