diff --git a/.github/workflows/integration-enterprise.yaml b/.github/workflows/integration-enterprise.yaml index 6df75e098..9cd5e3d37 100644 --- a/.github/workflows/integration-enterprise.yaml +++ b/.github/workflows/integration-enterprise.yaml @@ -14,9 +14,10 @@ jobs: - 'kong/kong-gateway:2.4.1.3' - 'kong/kong-gateway:2.5.1.2' - 'kong/kong-gateway:2.6.0.2' - - 'kong/kong-gateway:2.7.0.0' - - 'kong/kong-gateway:2.8.0.0' - - 'kong/kong-gateway:3.0.0.0' + - 'kong/kong-gateway:2.7' + - 'kong/kong-gateway:2.8' + - 'kong/kong-gateway:3.0' + - 'kong/kong-gateway:3.1' env: KONG_ANONYMOUS_REPORTS: "off" KONG_IMAGE: ${{ matrix.kong_image }} diff --git a/crud/registry.go b/crud/registry.go index 117d44e95..3d04ebfc8 100644 --- a/crud/registry.go +++ b/crud/registry.go @@ -101,7 +101,7 @@ func (r *Registry) Delete(ctx context.Context, kind Kind, args ...Arg) (Arg, err return res, nil } -// Do calls an aciton based on op with args and returns the result and error. +// Do calls an action based on op with args and returns the result and error. func (r *Registry) Do(ctx context.Context, kind Kind, op Op, args ...Arg) (Arg, error) { a, err := r.Get(kind) if err != nil { diff --git a/file/kong_json_schema.json b/file/kong_json_schema.json index 05c7435f2..4842b54d3 100644 --- a/file/kong_json_schema.json +++ b/file/kong_json_schema.json @@ -302,6 +302,12 @@ }, "name": { "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" } }, "additionalProperties": false, @@ -517,6 +523,12 @@ "$ref": "#/definitions/ConsumerGroupPlugin" }, "type": "array" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" } }, "additionalProperties": false, diff --git a/go.mod b/go.mod index 443b38a2a..1862586a7 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.2 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.13 - github.com/kong/go-kong v0.34.1-0.20221222170410-6c81ce561662 + github.com/kong/go-kong v0.35.0 github.com/mitchellh/go-homedir v1.1.0 github.com/shirou/gopsutil/v3 v3.22.12 github.com/spf13/cobra v1.6.1 @@ -27,7 +27,7 @@ require ( github.com/stretchr/testify v1.8.1 github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/sync v0.1.0 - k8s.io/code-generator v0.26.0 + k8s.io/code-generator v0.26.1 sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index 7f502f396..0f9d07ede 100644 --- a/go.sum +++ b/go.sum @@ -198,8 +198,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kong/go-kong v0.34.1-0.20221222170410-6c81ce561662 h1:IIIAzs6eNp+lA+E0k9oooAyqmAJyAA/9Ebyb0y19mrU= -github.com/kong/go-kong v0.34.1-0.20221222170410-6c81ce561662/go.mod h1:G30uJtuJOjJXFL1vulIrz/27KhPdE2g0GtJZlNINU6U= +github.com/kong/go-kong v0.35.0 h1:N+J1hZrRNL+92YDe/42M5fI5bjZSOHePq1p7OHWiUSU= +github.com/kong/go-kong v0.35.0/go.mod h1:4xT4uMMGcysH3qOqm458JyIbDBUw8MVAQIi+DP8ggtc= github.com/kong/semver/v4 v4.0.1 h1:DIcNR8W3gfx0KabFBADPalxxsp+q/5COwIFkkhrFQ2Y= github.com/kong/semver/v4 v4.0.1/go.mod h1:LImQ0oT15pJvSns/hs2laLca2zcYoHu5EsSNY0J6/QA= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -640,8 +640,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/code-generator v0.26.0 h1:ZDY+7Gic9p/lACgD1G72gQg2CvNGeAYZTPIncv+iALM= -k8s.io/code-generator v0.26.0/go.mod h1:OMoJ5Dqx1wgaQzKgc+ZWaZPfGjdRq/Y3WubFrZmeI3I= +k8s.io/code-generator v0.26.1 h1:dusFDsnNSKlMFYhzIM0jAO1OlnTN5WYwQQ+Ai12IIlo= +k8s.io/code-generator v0.26.1/go.mod h1:OMoJ5Dqx1wgaQzKgc+ZWaZPfGjdRq/Y3WubFrZmeI3I= k8s.io/gengo v0.0.0-20220902162205-c0856e24416d h1:U9tB195lKdzwqicbJvyJeOXV7Klv+wNAWENRnXEGi08= k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= diff --git a/konnect/consumer_group.go b/konnect/consumer_group.go index 8054794aa..f379c9daf 100644 --- a/konnect/consumer_group.go +++ b/konnect/consumer_group.go @@ -69,6 +69,25 @@ func CreateConsumerGroup(ctx context.Context, client *kong.Client, entity interf return &cg.Item, nil } +func UpdateConsumerGroup(ctx context.Context, client *kong.Client, + cgID *string, entity interface{}, +) (*kong.ConsumerGroup, error) { + if isEmptyString(cgID) { + return nil, fmt.Errorf("update consumer-group: consumer-group ID cannot be nil") + } + endpoint := fmt.Sprintf("/v1/consumer-groups/%v", *cgID) + req, err := client.NewRequest(http.MethodPut, endpoint, nil, entity) + if err != nil { + return nil, err + } + var cg konnectResponseObj + _, err = client.Do(ctx, req, &cg) + if err != nil { + return nil, err + } + return &cg.Item, nil +} + // GetConsumerGroup fetches a ConsumerGroup from Konnect. func GetConsumerGroup(ctx context.Context, client *kong.Client, nameOrID *string, @@ -96,7 +115,7 @@ func ListAllConsumerGroupMembers( ctx context.Context, client *kong.Client, cgID *string, ) ([]*kong.Consumer, error) { if isEmptyString(cgID) { - return nil, fmt.Errorf("list consumer-group members: consumer-group ID cannot be nil") + return nil, fmt.Errorf("list consumer-group-members: consumer-group ID cannot be nil") } var members, data []*kong.Consumer var err error @@ -149,7 +168,7 @@ func CreateRateLimitingAdvancedPlugin( ctx context.Context, client *kong.Client, cgID *string, config kong.Configuration, ) (*kong.ConsumerGroupRLA, error) { if isEmptyString(cgID) { - return nil, fmt.Errorf("update consumer-group override: consumer-group ID cannot be nil") + return nil, fmt.Errorf("create consumer-group override: consumer-group ID cannot be nil") } return upsertRateLimitingAdvancedPlugin( ctx, client, *cgID, config, http.MethodPost, @@ -214,7 +233,7 @@ func GetConsumerGroupRateLimitingAdvancedPlugin( }, nil } -// DeleteConsumerGroup deletes a ConsumerGroup plugin in Kong +// DeleteRateLimitingAdvancedPlugin deletes a ConsumerGroup plugin in Kong func DeleteRateLimitingAdvancedPlugin( ctx context.Context, client *kong.Client, cgID *string, ) error { @@ -259,7 +278,7 @@ func ListConsumerGroupMembers(ctx context.Context, return consumers, next, nil } -// Get fetches a ConsumerGroup from Kong. +// GetConsumerGroupObject Get fetches a ConsumerGroup from Kong. func GetConsumerGroupObject(ctx context.Context, client *kong.Client, cgID *string, ) (*kong.ConsumerGroupObject, error) { @@ -299,15 +318,15 @@ func GetConsumerGroupObject(ctx context.Context, return group, nil } -// Delete deletes a ConsumerGroup in Kong +// DeleteConsumerGroup deletes a ConsumerGroup in Kong func DeleteConsumerGroup( - ctx context.Context, client *kong.Client, nameOrID *string, + ctx context.Context, client *kong.Client, cgID *string, ) error { - if isEmptyString(nameOrID) { - return fmt.Errorf("deleting consumer-group: nameOrID cannot be nil") + if isEmptyString(cgID) { + return fmt.Errorf("delete consumer-group: ID cannot be nil") } - endpoint := fmt.Sprintf("/v1/consumer-groups/%v", *nameOrID) + endpoint := fmt.Sprintf("/v1/consumer-groups/%v", *cgID) req, err := client.NewRequest("DELETE", endpoint, nil, nil) if err != nil { return err @@ -321,7 +340,7 @@ func DeleteConsumerGroupMember( ctx context.Context, client *kong.Client, cgID, consumer *string, ) error { if isEmptyString(cgID) { - return fmt.Errorf("deleting consumer-group: nameOrID cannot be nil") + return fmt.Errorf("delete consumer-group-member: ID cannot be nil") } endpoint := fmt.Sprintf("/v1/consumers/%s/groups/%s/members", *consumer, *cgID) @@ -337,8 +356,10 @@ func DeleteConsumerGroupMember( func CreateConsumerGroupMember( ctx context.Context, client *kong.Client, cgID, consumer *string, ) error { - if isEmptyString(cgID) { - return fmt.Errorf("deleting consumer-group: nameOrID cannot be nil") + if isEmptyString(consumer) { + return fmt.Errorf("create consumer-group-member: consumer cannot be nil") + } else if isEmptyString(cgID) { + return fmt.Errorf("create consumer-group-member: consumer group ID cannot be nil") } endpoint := fmt.Sprintf("/v1/consumers/%s/groups/%s/members", *consumer, *cgID) @@ -351,6 +372,25 @@ func CreateConsumerGroupMember( return err } +func UpdateConsumerGroupMember( + ctx context.Context, client *kong.Client, cgID, consumer *string, +) error { + if isEmptyString(consumer) { + return fmt.Errorf("create consumer-group-member: consumer cannot be nil") + } else if isEmptyString(cgID) { + return fmt.Errorf("create consumer-group-member: consumer group ID cannot be nil") + } + + endpoint := fmt.Sprintf("/v1/consumers/%s/groups/%s/members", *consumer, *cgID) + req, err := client.NewRequest("PUT", endpoint, nil, nil) + if err != nil { + return err + } + + _, err = client.Do(ctx, req, nil) + return err +} + // list fetches a list of an entity in Kong. // opt can be used to control pagination. func list(ctx context.Context, diff --git a/state/consumer_group.go b/state/consumer_group.go index 4bf0dbb3b..823d122f0 100644 --- a/state/consumer_group.go +++ b/state/consumer_group.go @@ -45,7 +45,7 @@ func (k *ConsumerGroupsCollection) Add(consumerGroup ConsumerGroup) error { if !utils.Empty(consumerGroup.Name) { searchBy = append(searchBy, *consumerGroup.Name) } - _, err := getconsumerGroup(txn, searchBy...) + _, err := getConsumerGroup(txn, searchBy...) if err == nil { return fmt.Errorf("inserting consumerGroup %v: %w", consumerGroup.Console(), ErrAlreadyExists) } else if err != ErrNotFound { @@ -60,7 +60,7 @@ func (k *ConsumerGroupsCollection) Add(consumerGroup ConsumerGroup) error { return nil } -func getconsumerGroup(txn *memdb.Txn, IDs ...string) (*ConsumerGroup, error) { +func getConsumerGroup(txn *memdb.Txn, IDs ...string) (*ConsumerGroup, error) { for _, id := range IDs { res, err := multiIndexLookupUsingTxn(txn, consumerGroupTableName, []string{"name", "id"}, id) @@ -88,7 +88,7 @@ func (k *ConsumerGroupsCollection) Get(nameOrID string) (*ConsumerGroup, error) txn := k.db.Txn(false) defer txn.Abort() - consumerGroup, err := getconsumerGroup(txn, nameOrID) + consumerGroup, err := getConsumerGroup(txn, nameOrID) if err != nil { if err == ErrNotFound { return nil, ErrNotFound @@ -98,7 +98,7 @@ func (k *ConsumerGroupsCollection) Get(nameOrID string) (*ConsumerGroup, error) return consumerGroup, nil } -// Update udpates an existing consumerGroup. +// Update updates an existing consumerGroup. func (k *ConsumerGroupsCollection) Update(consumerGroup ConsumerGroup) error { if utils.Empty(consumerGroup.ID) { return errIDRequired @@ -107,7 +107,7 @@ func (k *ConsumerGroupsCollection) Update(consumerGroup ConsumerGroup) error { txn := k.db.Txn(true) defer txn.Abort() - err := deleteconsumerGroup(txn, *consumerGroup.ID) + err := deleteConsumerGroup(txn, *consumerGroup.ID) if err != nil { return err } @@ -121,8 +121,8 @@ func (k *ConsumerGroupsCollection) Update(consumerGroup ConsumerGroup) error { return nil } -func deleteconsumerGroup(txn *memdb.Txn, nameOrID string) error { - consumerGroup, err := getconsumerGroup(txn, nameOrID) +func deleteConsumerGroup(txn *memdb.Txn, nameOrID string) error { + consumerGroup, err := getConsumerGroup(txn, nameOrID) if err != nil { return err } @@ -143,7 +143,7 @@ func (k *ConsumerGroupsCollection) Delete(nameOrID string) error { txn := k.db.Txn(true) defer txn.Abort() - err := deleteconsumerGroup(txn, nameOrID) + err := deleteConsumerGroup(txn, nameOrID) if err != nil { return err } diff --git a/state/consumer_group_consumers.go b/state/consumer_group_consumers.go index 01e83adad..b2f1b7320 100644 --- a/state/consumer_group_consumers.go +++ b/state/consumer_group_consumers.go @@ -124,6 +124,7 @@ func getAllByConsumerGroupID(txn *memdb.Txn, consumerGroupID string) ([]*Consume } func getConsumerGroupConsumer(txn *memdb.Txn, consumerGroupID string, IDs ...string) (*ConsumerGroupConsumer, error) { + // TODO this could be a simple First command, to check for the username index consumers, err := getAllByConsumerGroupID(txn, consumerGroupID) if err != nil { return nil, err @@ -163,7 +164,7 @@ func (k *ConsumerGroupConsumersCollection) Update(consumer ConsumerGroupConsumer defer txn.Abort() res, err := multiIndexLookupUsingTxn(txn, consumerGroupConsumerTableName, - []string{"id", "username"}, *consumer.Consumer) + []string{"id", "username"}, *consumer.Consumer.ID, *consumer.ConsumerGroup.ID) if err != nil { return err } diff --git a/state/consumer_group_plugin.go b/state/consumer_group_plugin.go index 542d3c7bf..9f7e9eb22 100644 --- a/state/consumer_group_plugin.go +++ b/state/consumer_group_plugin.go @@ -163,7 +163,7 @@ func (k *ConsumerGroupPluginsCollection) Update(plugin ConsumerGroupPlugin) erro defer txn.Abort() res, err := multiIndexLookupUsingTxn(txn, consumerGroupPluginTableName, - []string{"id", "name"}, nameOrID) + []string{"id", "name"}, nameOrID, *plugin.ConsumerGroup.ID) if err != nil { return err } @@ -186,7 +186,7 @@ func (k *ConsumerGroupPluginsCollection) Update(plugin ConsumerGroupPlugin) erro return nil } -func deleteconsumerGroupPlugin(txn *memdb.Txn, nameOrID, consumerGroupID string) error { +func deleteConsumerGroupPlugin(txn *memdb.Txn, nameOrID, consumerGroupID string) error { consumer, err := getConsumerGroupPlugin(txn, consumerGroupID, nameOrID) if err != nil { return err @@ -211,7 +211,7 @@ func (k *ConsumerGroupPluginsCollection) Delete(nameOrID, consumerGroupID string txn := k.db.Txn(true) defer txn.Abort() - err := deleteconsumerGroupPlugin(txn, nameOrID, consumerGroupID) + err := deleteConsumerGroupPlugin(txn, nameOrID, consumerGroupID) if err != nil { return err } diff --git a/tests/integration/sync_test.go b/tests/integration/sync_test.go index dd1e279b3..e3d60cdf7 100644 --- a/tests/integration/sync_test.go +++ b/tests/integration/sync_test.go @@ -425,6 +425,34 @@ var ( }, } + consumerGroupsWithTags = []*kong.ConsumerGroupObject{ + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("silver"), + Tags: kong.StringSlice("tag1", "tag3"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("bar"), + }, + { + Username: kong.String("baz"), + }, + }, + }, + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("gold"), + Tags: kong.StringSlice("tag1", "tag2"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("foo"), + }, + }, + }, + } + consumerGroupsWithRLA = []*kong.ConsumerGroupObject{ { ConsumerGroup: &kong.ConsumerGroup{ @@ -476,10 +504,64 @@ var ( }, } + consumerGroupsWithTagsAndRLA = []*kong.ConsumerGroupObject{ + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("silver"), + Tags: kong.StringSlice("tag1", "tag3"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("bar"), + }, + }, + Plugins: []*kong.ConsumerGroupPlugin{ + { + Name: kong.String("rate-limiting-advanced"), + Config: kong.Configuration{ + "limit": []any{float64(7)}, + "retry_after_jitter_max": float64(1), + "window_size": []any{float64(60)}, + "window_type": "sliding", + }, + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("521a90ad-36cb-4e31-a5db-1d979aee40d1"), + }, + }, + }, + }, + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("gold"), + Tags: kong.StringSlice("tag1", "tag2"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("foo"), + }, + }, + Plugins: []*kong.ConsumerGroupPlugin{ + { + Name: kong.String("rate-limiting-advanced"), + Config: kong.Configuration{ + "limit": []any{float64(10)}, + "retry_after_jitter_max": float64(1), + "window_size": []any{float64(60)}, + "window_type": "sliding", + }, + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("92177268-b134-42f9-909a-36f9d2d3d5e7"), + }, + }, + }, + }, + } + consumerGroupsWithRLAKonnect = []*kong.ConsumerGroupObject{ { ConsumerGroup: &kong.ConsumerGroup{ Name: kong.String("silver"), + Tags: kong.StringSlice("tag1", "tag3"), }, Consumers: []*kong.Consumer{ { @@ -504,6 +586,7 @@ var ( { ConsumerGroup: &kong.ConsumerGroup{ Name: kong.String("gold"), + Tags: kong.StringSlice("tag1", "tag2"), }, Consumers: []*kong.Consumer{ { @@ -2658,24 +2741,27 @@ func Test_Sync_ConsumerGroups_31(t *testing.T) { t.Errorf(err.Error()) } tests := []struct { - name string - kongFile string - expectedState utils.KongRawState + name string + kongFile string + kongFileInitial string + expectedState utils.KongRawState }{ { - name: "creates consumer groups", - kongFile: "testdata/sync/015-consumer-groups/kong3x.yaml", + name: "creates consumer groups", + kongFile: "testdata/sync/015-consumer-groups/kong3x.yaml", + kongFileInitial: "testdata/sync/015-consumer-groups/kong3x-initial.yaml", expectedState: utils.KongRawState{ Consumers: consumerGroupsConsumers, - ConsumerGroups: consumerGroups, + ConsumerGroups: consumerGroupsWithTags, }, }, { - name: "creates consumer groups and plugin", - kongFile: "testdata/sync/016-consumer-groups-and-plugins/kong3x.yaml", + name: "creates consumer groups and plugin", + kongFile: "testdata/sync/016-consumer-groups-and-plugins/kong3x.yaml", + kongFileInitial: "testdata/sync/016-consumer-groups-and-plugins/kong3x-initial.yaml", expectedState: utils.KongRawState{ Consumers: consumerGroupsConsumers, - ConsumerGroups: consumerGroupsWithRLA, + ConsumerGroups: consumerGroupsWithTagsAndRLA, }, }, } @@ -2685,7 +2771,11 @@ func Test_Sync_ConsumerGroups_31(t *testing.T) { teardown := setup(t) defer teardown(t) + // set up initial state + sync(tc.kongFileInitial) + // update with desired final state sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) }) } @@ -2716,7 +2806,7 @@ func Test_Sync_ConsumerGroups_31(t *testing.T) { // every 30s, while consumers belonging to the 'gold' and 'silver' consumer groups // should be allowed to run respectively 10 and 7 requests in the same timeframe. // In order to make sure this is the case, we run requests in a loop -// for all consumers consumers and then check at what point they start to receive 429. +// for all consumers and then check at what point they start to receive 429. func Test_Sync_ConsumerGroupsRLAFrom31(t *testing.T) { const ( maxGoldRequestsNumber = 10 @@ -2838,21 +2928,24 @@ func Test_Sync_ConsumerGroupsKonnect(t *testing.T) { t.Errorf(err.Error()) } tests := []struct { - name string - kongFile string - expectedState utils.KongRawState + name string + kongFile string + kongFileInitial string + expectedState utils.KongRawState }{ { - name: "creates consumer groups", - kongFile: "testdata/sync/015-consumer-groups/kong3x.yaml", + name: "creates consumer groups", + kongFile: "testdata/sync/015-consumer-groups/kong3x.yaml", + kongFileInitial: "testdata/sync/015-consumer-groups/kong3x-initial.yaml", expectedState: utils.KongRawState{ Consumers: consumerGroupsConsumers, - ConsumerGroups: consumerGroups, + ConsumerGroups: consumerGroupsWithTags, }, }, { - name: "creates consumer groups and plugin", - kongFile: "testdata/sync/016-consumer-groups-and-plugins/kong3x.yaml", + name: "creates consumer groups and plugin", + kongFile: "testdata/sync/016-consumer-groups-and-plugins/kong3x.yaml", + kongFileInitial: "testdata/sync/016-consumer-groups-and-plugins/kong3x-initial.yaml", expectedState: utils.KongRawState{ Consumers: consumerGroupsConsumers, ConsumerGroups: consumerGroupsWithRLAKonnect, @@ -2865,7 +2958,11 @@ func Test_Sync_ConsumerGroupsKonnect(t *testing.T) { teardown := setup(t) defer teardown(t) + // set up initial state + sync(tc.kongFileInitial) + // update with desired final state sync(tc.kongFile) + testKongState(t, client, true, tc.expectedState, nil) }) } diff --git a/tests/integration/testdata/sync/015-consumer-groups/kong3x-initial.yaml b/tests/integration/testdata/sync/015-consumer-groups/kong3x-initial.yaml new file mode 100644 index 000000000..388eb8f2e --- /dev/null +++ b/tests/integration/testdata/sync/015-consumer-groups/kong3x-initial.yaml @@ -0,0 +1,18 @@ +_format_version: "3.0" +consumer_groups: +- name: silver + tags: + - tag1 +- name: gold + tags: + - tag1 +consumers: +- username: foo + groups: + - name: gold +- username: bar + groups: + - name: silver +- username: baz + groups: + - name: silver diff --git a/tests/integration/testdata/sync/015-consumer-groups/kong3x.yaml b/tests/integration/testdata/sync/015-consumer-groups/kong3x.yaml index 00f479883..b50eeac90 100644 --- a/tests/integration/testdata/sync/015-consumer-groups/kong3x.yaml +++ b/tests/integration/testdata/sync/015-consumer-groups/kong3x.yaml @@ -1,7 +1,13 @@ _format_version: "3.0" consumer_groups: - name: silver + tags: + - tag1 + - tag3 - name: gold + tags: + - tag1 + - tag2 consumers: - username: foo groups: diff --git a/tests/integration/testdata/sync/016-consumer-groups-and-plugins/kong3x-initial.yaml b/tests/integration/testdata/sync/016-consumer-groups-and-plugins/kong3x-initial.yaml new file mode 100644 index 000000000..e08e3b262 --- /dev/null +++ b/tests/integration/testdata/sync/016-consumer-groups-and-plugins/kong3x-initial.yaml @@ -0,0 +1,35 @@ +_format_version: "3.0" +consumer_groups: +- name: gold + tags: + - tag1 + - tag2 + plugins: + - name: rate-limiting-advanced + config: + limit: + - 20 + retry_after_jitter_max: 1 + window_size: + - 50 + window_type: sliding +- name: silver + tags: + - tag1 + plugins: + - name: rate-limiting-advanced + config: + limit: + - 10 + retry_after_jitter_max: 1 + window_size: + - 50 + window_type: sliding +consumers: +- groups: + - name: gold + username: bar +- username: baz +- groups: + - name: gold + username: foo diff --git a/tests/integration/testdata/sync/016-consumer-groups-and-plugins/kong3x.yaml b/tests/integration/testdata/sync/016-consumer-groups-and-plugins/kong3x.yaml index 2e14fa14b..592b15958 100644 --- a/tests/integration/testdata/sync/016-consumer-groups-and-plugins/kong3x.yaml +++ b/tests/integration/testdata/sync/016-consumer-groups-and-plugins/kong3x.yaml @@ -1,9 +1,11 @@ _format_version: "3.0" consumer_groups: -- id: 92177268-b134-42f9-909a-36f9d2d3d5e7 - name: gold +- name: gold + tags: + - tag1 + - tag2 plugins: - - id: cc3e0973-1dab-4887-b164-9ae75c12c0bb + - name: rate-limiting-advanced config: limit: - 10 @@ -11,11 +13,12 @@ consumer_groups: window_size: - 60 window_type: sliding - name: rate-limiting-advanced -- id: 521a90ad-36cb-4e31-a5db-1d979aee40d1 - name: silver +- name: silver + tags: + - tag1 + - tag3 plugins: - - id: 54c98a55-b464-46f3-9ae4-96ff74a35ac1 + - name: rate-limiting-advanced config: limit: - 7 @@ -23,15 +26,11 @@ consumer_groups: window_size: - 60 window_type: sliding - name: rate-limiting-advanced consumers: - groups: - name: silver username: bar - id: 5a5b9369-baeb-4faa-a902-c40ccdc2928e - username: baz - id: e894ea9e-ad08-4acf-a960-5a23aa7701c7 - groups: - name: gold username: foo - id: 87095815-5395-454e-8c18-a11c9bc0ef04 diff --git a/types/consumer_group.go b/types/consumer_group.go index 34e2003b7..a8403d41c 100644 --- a/types/consumer_group.go +++ b/types/consumer_group.go @@ -31,18 +31,16 @@ func consumerGroupFromStruct(arg crud.Event) *state.ConsumerGroup { func (s *consumerGroupCRUD) Create(ctx context.Context, arg ...crud.Arg) (crud.Arg, error) { event := crud.EventFromArg(arg[0]) consumerGroup := consumerGroupFromStruct(event) + var createdConsumerGroup *kong.ConsumerGroup var err error if s.isKonnect { createdConsumerGroup, err = konnect.CreateConsumerGroup(ctx, s.client, &consumerGroup.ConsumerGroup) - if err != nil { - return nil, err - } } else { createdConsumerGroup, err = s.client.ConsumerGroups.Create(ctx, &consumerGroup.ConsumerGroup) - if err != nil { - return nil, err - } + } + if err != nil { + return nil, err } return &state.ConsumerGroup{ConsumerGroup: *createdConsumerGroup}, nil } @@ -50,20 +48,19 @@ func (s *consumerGroupCRUD) Create(ctx context.Context, arg ...crud.Arg) (crud.A // Delete deletes a consumerGroup in Kong. // The arg should be of type crud.Event, containing the consumerGroup to be deleted, // else the function will panic. -// It returns a the deleted *state.consumerGroup. +// It returns the deleted *state.consumerGroup. func (s *consumerGroupCRUD) Delete(ctx context.Context, arg ...crud.Arg) (crud.Arg, error) { event := crud.EventFromArg(arg[0]) consumerGroup := consumerGroupFromStruct(event) + + var err error if s.isKonnect { - err := konnect.DeleteConsumerGroup(ctx, s.client, consumerGroup.ConsumerGroup.ID) - if err != nil { - return nil, err - } + err = konnect.DeleteConsumerGroup(ctx, s.client, consumerGroup.ConsumerGroup.ID) } else { - err := s.client.ConsumerGroups.Delete(ctx, consumerGroup.ConsumerGroup.ID) - if err != nil { - return nil, err - } + err = s.client.ConsumerGroups.Delete(ctx, consumerGroup.ConsumerGroup.ID) + } + if err != nil { + return nil, err } return consumerGroup, nil } @@ -71,16 +68,22 @@ func (s *consumerGroupCRUD) Delete(ctx context.Context, arg ...crud.Arg) (crud.A // Update updates a consumerGroup in Kong. // The arg should be of type crud.Event, containing the consumerGroup to be updated, // else the function will panic. -// It returns a the updated *state.consumerGroup. +// It returns the updated *state.consumerGroup. func (s *consumerGroupCRUD) Update(ctx context.Context, arg ...crud.Arg) (crud.Arg, error) { event := crud.EventFromArg(arg[0]) consumerGroup := consumerGroupFromStruct(event) - updatedconsumerGroup, err := s.client.ConsumerGroups.Create(ctx, &consumerGroup.ConsumerGroup) + var err error + var updatedConsumerGroup *kong.ConsumerGroup + if s.isKonnect { + updatedConsumerGroup, err = konnect.UpdateConsumerGroup(ctx, s.client, consumerGroup.ID, &consumerGroup.ConsumerGroup) + } else { + updatedConsumerGroup, err = s.client.ConsumerGroups.Create(ctx, &consumerGroup.ConsumerGroup) + } if err != nil { return nil, err } - return &state.ConsumerGroup{ConsumerGroup: *updatedconsumerGroup}, nil + return &state.ConsumerGroup{ConsumerGroup: *updatedConsumerGroup}, nil } type consumerGroupDiffer struct { diff --git a/types/consumer_group_consumer.go b/types/consumer_group_consumer.go index 197f80ec7..cc7a0a898 100644 --- a/types/consumer_group_consumer.go +++ b/types/consumer_group_consumer.go @@ -31,19 +31,19 @@ func consumerGroupConsumerFromStruct(arg crud.Event) *state.ConsumerGroupConsume func (s *consumerGroupConsumerCRUD) Create(ctx context.Context, arg ...crud.Arg) (crud.Arg, error) { event := crud.EventFromArg(arg[0]) consumer := consumerGroupConsumerFromStruct(event) + + var err error if s.isKonnect { - err := konnect.CreateConsumerGroupMember( + err = konnect.CreateConsumerGroupMember( ctx, s.client, consumer.ConsumerGroup.ID, consumer.Consumer.ID, ) - if err != nil { - return nil, err - } } else { - _, err := s.client.ConsumerGroupConsumers.Create(ctx, consumer.ConsumerGroup.ID, consumer.Consumer.Username) - if err != nil { - return nil, err - } + _, err = s.client.ConsumerGroupConsumers.Create(ctx, consumer.ConsumerGroup.ID, consumer.Consumer.Username) } + if err != nil { + return nil, err + } + return &state.ConsumerGroupConsumer{ ConsumerGroupConsumer: kong.ConsumerGroupConsumer{ Consumer: consumer.Consumer, @@ -55,38 +55,62 @@ func (s *consumerGroupConsumerCRUD) Create(ctx context.Context, arg ...crud.Arg) // Delete deletes a consumerGroupConsumer in Kong. // The arg should be of type crud.Event, containing the consumerGroupConsumer to be deleted, // else the function will panic. -// It returns a the deleted *state.consumerGroupConsumer. +// It returns the deleted *state.consumerGroupConsumer. func (s *consumerGroupConsumerCRUD) Delete(ctx context.Context, arg ...crud.Arg) (crud.Arg, error) { event := crud.EventFromArg(arg[0]) consumer := consumerGroupConsumerFromStruct(event) + + var err error if s.isKonnect { - err := konnect.DeleteConsumerGroupMember(ctx, s.client, consumer.ConsumerGroup.ID, consumer.Consumer.ID) - if err != nil { - return nil, err - } + err = konnect.DeleteConsumerGroupMember(ctx, s.client, consumer.ConsumerGroup.ID, consumer.Consumer.ID) } else { - err := s.client.ConsumerGroupConsumers.Delete(ctx, consumer.ConsumerGroup.ID, consumer.Consumer.Username) - if err != nil { - return nil, err - } + err = s.client.ConsumerGroupConsumers.Delete(ctx, consumer.ConsumerGroup.ID, consumer.Consumer.Username) } + if err != nil { + return nil, err + } + return consumer, nil } // Update updates a consumerGroupConsumer in Kong. // The arg should be of type crud.Event, containing the consumerGroupConsumer to be updated, // else the function will panic. -// It returns a the updated *state.consumerGroupConsumer. +// It returns the updated *state.consumerGroupConsumer. func (s *consumerGroupConsumerCRUD) Update(ctx context.Context, arg ...crud.Arg) (crud.Arg, error) { event := crud.EventFromArg(arg[0]) consumer := consumerGroupConsumerFromStruct(event) - _, err := s.client.ConsumerGroupConsumers.Create( - ctx, consumer.ConsumerGroup.ID, consumer.Consumer.Username, - ) + var err error + // delete the old member + // TODO can this fail? + if s.isKonnect { + err = konnect.DeleteConsumerGroupMember( + ctx, s.client, consumer.ConsumerGroup.ID, consumer.Consumer.ID, + ) + } else { + err = s.client.ConsumerGroupConsumers.Delete( + ctx, consumer.ConsumerGroup.ID, consumer.Consumer.Username, + ) + } + if err != nil { + return nil, err + } + + // recreate it + if s.isKonnect { + err = konnect.CreateConsumerGroupMember( + ctx, s.client, consumer.ConsumerGroup.ID, consumer.Consumer.ID, + ) + } else { + _, err = s.client.ConsumerGroupConsumers.Create( + ctx, consumer.ConsumerGroup.ID, consumer.Consumer.Username, + ) + } if err != nil { return nil, err } + return &state.ConsumerGroupConsumer{ ConsumerGroupConsumer: kong.ConsumerGroupConsumer{ Consumer: consumer.Consumer, diff --git a/types/consumer_group_plugin.go b/types/consumer_group_plugin.go index 4d94ef0ed..be7fa791a 100644 --- a/types/consumer_group_plugin.go +++ b/types/consumer_group_plugin.go @@ -63,7 +63,7 @@ func (s *consumerGroupPluginCRUD) Create(ctx context.Context, arg ...crud.Arg) ( // Update updates a consumerGroupConsumer in Kong. // The arg should be of type crud.Event, containing the consumerGroupConsumer to be updated, // else the function will panic. -// It returns a the updated *state.consumerGroupConsumer. +// It returns the updated *state.consumerGroupConsumer. func (s *consumerGroupPluginCRUD) Update(ctx context.Context, arg ...crud.Arg) (crud.Arg, error) { event := crud.EventFromArg(arg[0]) plugin := consumerGroupPluginFromStruct(event) @@ -87,10 +87,12 @@ func (s *consumerGroupPluginCRUD) Update(ctx context.Context, arg ...crud.Arg) ( } return &state.ConsumerGroupPlugin{ ConsumerGroupPlugin: kong.ConsumerGroupPlugin{ + ID: plugin.ID, Name: res.Plugin, Config: res.Config, ConsumerGroup: &kong.ConsumerGroup{ - ID: res.ConsumerGroup, + ID: plugin.ConsumerGroup.ID, + Name: res.ConsumerGroup, }, }, }, nil