diff --git a/cmd/common.go b/cmd/common.go index 513cbdb78..d816e34ff 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -248,6 +248,7 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism, // read the current state var currentState *state.KongState if workspaceExists { + ctx = context.WithValue(ctx, utils.KongVersionContextKey, parsedKongVersion) currentState, err = fetchCurrentState(ctx, kongClient, dumpConfig) if err != nil { return err diff --git a/dump/dump.go b/dump/dump.go index eeb59d441..1559b1f48 100644 --- a/dump/dump.go +++ b/dump/dump.go @@ -73,7 +73,14 @@ func getConsumerGroupsConfiguration(ctx context.Context, group *errgroup.Group, client *kong.Client, config Config, state *utils.KongRawState, ) { group.Go(func() error { - consumerGroups, err := GetAllConsumerGroups(ctx, client, config.SelectorTags) + var handleTags bool + kongVersionFromContext := utils.GetKongVersionFromContext(ctx) + if kongVersionFromContext.LT(utils.Kong320Version) { + handleTags = true + } + consumerGroups, err := GetAllConsumerGroups( + ctx, client, config.SelectorTags, handleTags, + ) if err != nil { if kong.IsNotFoundErr(err) || kong.IsForbiddenErr(err) { return nil @@ -523,12 +530,35 @@ func GetAllUpstreams(ctx context.Context, return upstreams, nil } +func containsTags(inputTagsMap map[string]bool, tags []*string) bool { + if len(inputTagsMap) == 0 { + // no tags are present in input, so no filtering is expected. + return true + } + if len(tags) == 0 { + // entity is not tagged, but tags were present in the request. + return false + } + for _, cgTag := range tags { + if inputTagsMap[*cgTag] { + return true + } + } + return false +} + // GetAllConsumerGroups queries Kong for all the ConsumerGroups using client. func GetAllConsumerGroups(ctx context.Context, - client *kong.Client, tags []string, + client *kong.Client, tags []string, handleTags bool, ) ([]*kong.ConsumerGroupObject, error) { var consumerGroupObjects []*kong.ConsumerGroupObject opt := newOpt(tags) + tagsLen := len(tags) + + tagsMap := make(map[string]bool, tagsLen) + for _, tag := range tags { + tagsMap[tag] = true + } for { cgs, nextopt, err := client.ConsumerGroups.List(ctx, opt) @@ -540,6 +570,10 @@ func GetAllConsumerGroups(ctx context.Context, } for _, cg := range cgs { + if handleTags && !containsTags(tagsMap, cg.Tags) { + continue + } + r, err := client.ConsumerGroups.Get(ctx, cg.Name) if err != nil { return nil, err diff --git a/tests/integration/sync_test.go b/tests/integration/sync_test.go index 5a143ead7..6c97159af 100644 --- a/tests/integration/sync_test.go +++ b/tests/integration/sync_test.go @@ -4814,3 +4814,50 @@ func Test_Sync_DoNotUpdateCreatedAt(t *testing.T) { // plugins do not have an updated_at field // consumers do not have an updated_at field } + +// test scope: +// - 2.8.0+ +func Test_Sync_ConsumerGroupsWithTags(t *testing.T) { + runWhen(t, "enterprise", ">=2.8.0") + setup(t) + + client, err := getTestClient() + if err != nil { + t.Fatalf(err.Error()) + } + + const ( + noTags = "testdata/sync/028-consumer-groups-tags/no_tags.yaml" + withTags = "testdata/sync/028-consumer-groups-tags/with_tags.yaml" + ) + + // create a consumer-group and a plugin with no tags + require.NoError(t, sync(noTags)) + + // get the current state + ctx := context.Background() + oldKongState, err := deckDump.Get(ctx, client, deckDump.Config{}) + if err != nil { + t.Errorf(err.Error()) + } + + // create entities with select_tags + time.Sleep(time.Second) + require.NoError(t, sync(withTags)) + + // get the new state + newKongState, err := deckDump.Get(ctx, client, deckDump.Config{}) + if err != nil { + t.Errorf(err.Error()) + } + + // verify that entities with no tags were not wiped out by the sync with select_tags + require.Equal(t, len(oldKongState.ConsumerGroups), len(newKongState.ConsumerGroups)) + require.Equal( + t, + oldKongState.ConsumerGroups[0].ConsumerGroup.CreatedAt, + newKongState.ConsumerGroups[0].ConsumerGroup.CreatedAt, + ) + require.Equal(t, len(oldKongState.Plugins), len(newKongState.Plugins)) + require.Equal(t, oldKongState.Plugins[0].CreatedAt, newKongState.Plugins[0].CreatedAt) +} diff --git a/tests/integration/testdata/sync/028-consumer-groups-tags/no_tags.yaml b/tests/integration/testdata/sync/028-consumer-groups-tags/no_tags.yaml new file mode 100644 index 000000000..1a86badf3 --- /dev/null +++ b/tests/integration/testdata/sync/028-consumer-groups-tags/no_tags.yaml @@ -0,0 +1,9 @@ +_format_version: "3.0" +consumer_groups: +- name: my-cg + consumers: + - username: foo +plugins: +- name: prometheus +consumers: +- username: foo \ No newline at end of file diff --git a/tests/integration/testdata/sync/028-consumer-groups-tags/with_tags.yaml b/tests/integration/testdata/sync/028-consumer-groups-tags/with_tags.yaml new file mode 100644 index 000000000..946b89d8b --- /dev/null +++ b/tests/integration/testdata/sync/028-consumer-groups-tags/with_tags.yaml @@ -0,0 +1,15 @@ +_format_version: "3.0" +_info: + defaults: {} + select_tags: + - foo +services: +- connect_timeout: 60000 + enabled: true + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 \ No newline at end of file diff --git a/utils/utils.go b/utils/utils.go index f87d034f3..02a477637 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -22,9 +22,26 @@ var ( Kong140Version = semver.MustParse("1.4.0") Kong300Version = semver.MustParse("3.0.0") + Kong320Version = semver.MustParse("3.2.0") Kong340Version = semver.MustParse("3.4.0") ) +type ContextKey int + +const ( + // KongVersionContextKey is the context key used to store the Kong version + // in the context. + KongVersionContextKey ContextKey = iota +) + +func GetKongVersionFromContext(ctx context.Context) semver.Version { + v, ok := ctx.Value(KongVersionContextKey).(semver.Version) + if !ok { + return semver.Version{} + } + return v +} + var ErrorConsumerGroupUpgrade = errors.New( "a rate-limiting-advanced plugin with config.consumer_groups\n" + "and/or config.enforce_consumer_groups was found. Please use Consumer Groups scoped\n" +