From 352e6280ea9418c32141613cd943dd9af01e428b Mon Sep 17 00:00:00 2001 From: Kyrie Chen Date: Wed, 14 Dec 2022 15:32:45 -0500 Subject: [PATCH] Support Tag and Untag APIs for User resource. --- apis/v1alpha1/ack-generate-metadata.yaml | 4 +- apis/v1alpha1/generator.yaml | 2 + generator.yaml | 2 + pkg/resource/user/hooks.go | 141 +++++++++++++++++- pkg/resource/user/sdk.go | 19 +++ .../user/sdk_read_many_post_set_output.go.tpl | 6 + .../user/sdk_update_pre_build_request.go.tpl | 19 ++- .../scenarios/User/user_update_with_tags.yaml | 72 +++++++++ 8 files changed, 258 insertions(+), 7 deletions(-) create mode 100644 templates/hooks/user/sdk_read_many_post_set_output.go.tpl create mode 100644 test/e2e/scenarios/User/user_update_with_tags.yaml diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index 7778928..dd227d2 100755 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,5 +1,5 @@ ack_generate_info: - build_date: "2022-12-23T06:27:19Z" + build_date: "2022-12-23T17:00:07Z" build_hash: 16f0e201b37a06b535370cc69e11adb934a22d33 go_version: go1.19 version: v0.20.1-18-g16f0e20 @@ -7,7 +7,7 @@ api_directory_checksum: a1e396caca4bdd1612fa7d09f0ee56f3e4976ff7 api_version: v1alpha1 aws_sdk_go_version: v1.44.93 generator_config_info: - file_checksum: 62ca61f60f6152f6c53f31b6ebbe43b39f86d225 + file_checksum: f50534e33903e1ca74491595fd6c21351988eb1c original_file_name: generator.yaml last_modification: reason: API generation diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index e44bd89..e7f37eb 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -165,6 +165,8 @@ resources: hooks: sdk_create_post_set_output: code: "rm.setAnnotationsFields(desired, ko)" + sdk_read_many_post_set_output: + template_path: hooks/user/sdk_read_many_post_set_output.go.tpl sdk_update_post_set_output: code: "rm.setAnnotationsFields(desired, ko)" sdk_update_pre_build_request: diff --git a/generator.yaml b/generator.yaml index e44bd89..e7f37eb 100644 --- a/generator.yaml +++ b/generator.yaml @@ -165,6 +165,8 @@ resources: hooks: sdk_create_post_set_output: code: "rm.setAnnotationsFields(desired, ko)" + sdk_read_many_post_set_output: + template_path: hooks/user/sdk_read_many_post_set_output.go.tpl sdk_update_post_set_output: code: "rm.setAnnotationsFields(desired, ko)" sdk_update_pre_build_request: diff --git a/pkg/resource/user/hooks.go b/pkg/resource/user/hooks.go index 5f63514..8a774a5 100644 --- a/pkg/resource/user/hooks.go +++ b/pkg/resource/user/hooks.go @@ -14,10 +14,16 @@ package user import ( - ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" + "context" "github.com/pkg/errors" + svcapitypes "github.com/aws-controllers-k8s/memorydb-controller/apis/v1alpha1" + ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare" "github.com/aws-controllers-k8s/runtime/pkg/requeue" + ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log" + ackutil "github.com/aws-controllers-k8s/runtime/pkg/util" + + svcsdk "github.com/aws/aws-sdk-go/service/memorydb" ) // validateUserNeedsUpdate this function's purpose is to requeue if the resource is currently unavailable and @@ -48,3 +54,136 @@ func (rm *resourceManager) validateUserNeedsUpdate( return nil, nil } + +// getTags gets tags from given ParameterGroup. +func (rm *resourceManager) getTags( + ctx context.Context, + resourceARN string, +) ([]*svcapitypes.Tag, error) { + resp, err := rm.sdkapi.ListTagsWithContext( + ctx, + &svcsdk.ListTagsInput{ + ResourceArn: &resourceARN, + }, + ) + rm.metrics.RecordAPICall("GET", "ListTags", err) + if err != nil { + return nil, err + } + tags := make([]*svcapitypes.Tag, 0, len(resp.TagList)) + for _, tag := range resp.TagList { + tags = append(tags, &svcapitypes.Tag{ + Key: tag.Key, + Value: tag.Value, + }) + } + return tags, nil +} + +// updateTags updates tags of given ParameterGroup to desired tags. +func (rm *resourceManager) updateTags( + ctx context.Context, + desired *resource, + latest *resource, +) (err error) { + rlog := ackrtlog.FromContext(ctx) + exit := rlog.Trace("rm.updateTags") + defer func(err error) { exit(err) }(err) + + arn := (*string)(latest.ko.Status.ACKResourceMetadata.ARN) + + toAdd, toDelete := computeTagsDelta( + desired.ko.Spec.Tags, latest.ko.Spec.Tags, + ) + + if len(toDelete) > 0 { + rlog.Debug("removing tags from user", "tags", toDelete) + _, err = rm.sdkapi.UntagResourceWithContext( + ctx, + &svcsdk.UntagResourceInput{ + ResourceArn: arn, + TagKeys: toDelete, + }, + ) + rm.metrics.RecordAPICall("UPDATE", "UntagResource", err) + if err != nil { + return err + } + } + + if len(toAdd) > 0 { + rlog.Debug("adding tags to user", "tags", toAdd) + _, err = rm.sdkapi.TagResourceWithContext( + ctx, + &svcsdk.TagResourceInput{ + ResourceArn: arn, + Tags: sdkTagsFromResourceTags(toAdd), + }, + ) + rm.metrics.RecordAPICall("UPDATE", "TagResource", err) + if err != nil { + return err + } + } + + return nil +} + +func computeTagsDelta( + desired []*svcapitypes.Tag, + latest []*svcapitypes.Tag, +) (addedOrUpdated []*svcapitypes.Tag, removed []*string) { + var visitedIndexes []string + + for _, latestElement := range latest { + visitedIndexes = append(visitedIndexes, *latestElement.Key) + for _, desiredElement := range desired { + if equalStrings(latestElement.Key, desiredElement.Key) { + if !equalStrings(latestElement.Value, desiredElement.Value) { + addedOrUpdated = append(addedOrUpdated, desiredElement) + } + continue + } + } + removed = append(removed, latestElement.Key) + } + for _, desiredElement := range desired { + if !ackutil.InStrings(*desiredElement.Key, visitedIndexes) { + addedOrUpdated = append(addedOrUpdated, desiredElement) + } + } + return addedOrUpdated, removed +} + +func sdkTagsFromResourceTags( + rTags []*svcapitypes.Tag, +) []*svcsdk.Tag { + tags := make([]*svcsdk.Tag, len(rTags)) + for i := range rTags { + tags[i] = &svcsdk.Tag{ + Key: rTags[i].Key, + Value: rTags[i].Value, + } + } + return tags +} + +func resourceTagsFromSDKTags( + sdkTags []*svcsdk.Tag, +) []*svcapitypes.Tag { + tags := make([]*svcapitypes.Tag, len(sdkTags)) + for i := range sdkTags { + tags[i] = &svcapitypes.Tag{ + Key: sdkTags[i].Key, + Value: sdkTags[i].Value, + } + } + return tags +} + +func equalStrings(a, b *string) bool { + if a == nil { + return b == nil || *b == "" + } + return (*a == "" && b == nil) || *a == *b +} diff --git a/pkg/resource/user/sdk.go b/pkg/resource/user/sdk.go index 53213b6..2ad44e2 100644 --- a/pkg/resource/user/sdk.go +++ b/pkg/resource/user/sdk.go @@ -146,6 +146,13 @@ func (rm *resourceManager) sdkFind( } rm.setStatusDefaults(ko) + resourceARN := (*string)(ko.Status.ACKResourceMetadata.ARN) + tags, err := rm.getTags(ctx, *resourceARN) + if err != nil { + return nil, err + } + ko.Spec.Tags = tags + return &resource{ko}, nil } @@ -331,6 +338,18 @@ func (rm *resourceManager) sdkUpdate( if err != nil || res != nil { return res, err } + + if delta.DifferentAt("Spec.Tags") { + err = rm.updateTags(ctx, desired, latest) + if err != nil { + return nil, err + } + } + + if !delta.DifferentExcept("Spec.Tags") { + return desired, nil + } + input, err := rm.newUpdateRequestPayload(ctx, desired) if err != nil { return nil, err diff --git a/templates/hooks/user/sdk_read_many_post_set_output.go.tpl b/templates/hooks/user/sdk_read_many_post_set_output.go.tpl new file mode 100644 index 0000000..35ebff1 --- /dev/null +++ b/templates/hooks/user/sdk_read_many_post_set_output.go.tpl @@ -0,0 +1,6 @@ + resourceARN := (*string)(ko.Status.ACKResourceMetadata.ARN) + tags, err := rm.getTags(ctx, *resourceARN) + if err != nil { + return nil, err + } + ko.Spec.Tags = tags diff --git a/templates/hooks/user/sdk_update_pre_build_request.go.tpl b/templates/hooks/user/sdk_update_pre_build_request.go.tpl index 8efbaab..3afa4f8 100644 --- a/templates/hooks/user/sdk_update_pre_build_request.go.tpl +++ b/templates/hooks/user/sdk_update_pre_build_request.go.tpl @@ -1,5 +1,16 @@ -res, err := rm.validateUserNeedsUpdate(desired, latest, delta) + res, err := rm.validateUserNeedsUpdate(desired, latest, delta) -if err != nil || res!= nil{ - return res, err -} \ No newline at end of file + if err != nil || res!= nil{ + return res, err + } + + if delta.DifferentAt("Spec.Tags") { + err = rm.updateTags(ctx, desired, latest) + if err != nil { + return nil, err + } + } + + if !delta.DifferentExcept("Spec.Tags") { + return desired, nil + } diff --git a/test/e2e/scenarios/User/user_update_with_tags.yaml b/test/e2e/scenarios/User/user_update_with_tags.yaml new file mode 100644 index 0000000..ca3caf5 --- /dev/null +++ b/test/e2e/scenarios/User/user_update_with_tags.yaml @@ -0,0 +1,72 @@ +id: "USER_UPDATE_WITH_TAGS" +description: "In this test we create User and update its tags" +#marks: +# - slow +# - blocked +resource: + apiVersion: $CRD_GROUP/$CRD_VERSION + kind: User + metadata: + name: user$RANDOM_SUFFIX +steps: + - id: "USER_INITIAL_CREATE" + description: "Create User" + create: + spec: + name: user$RANDOM_SUFFIX + accessString: on +get + authenticationMode: + type_: password + passwords: + - key: password + name: $SECRET1 + wait: + status: + conditions: + ACK.ResourceSynced: + status: "True" + timeout: 100 + - id: "USER_ADD_TAGS" + description: "Add tags in User" + patch: + spec: + tags: + - key: "test_key_1" + value: "test_value_1" + - key: "test_key_2" + - key: + wait: + status: + conditions: + ACK.ResourceSynced: + status: "True" + timeout: 100 + - id: "USER_DELETE_TAGS" + description: "Delete tags in User" + patch: + spec: + tags: + - key: "test_key_1" + value: "test_value_1" + wait: + status: + conditions: + ACK.ResourceSynced: + status: "True" + timeout: 100 + - id: "SG_ADD_AND_DELETE_TAGS" + description: "Add some tags and delete some tags in User" + patch: + spec: + tags: + - key: "test_key_2" + value: "test_value_2" + wait: + status: + conditions: + ACK.ResourceSynced: + status: "True" + timeout: 100 + - id: "DELETE_USER" + description: "Delete User" + delete: user$RANDOM_SUFFIX