Skip to content

Commit 9410c3e

Browse files
authored
feat: Add support for LogGroup Tags Update (#42)
Issue [#2163](aws-controllers-k8s/community#2163) By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 21f9af1 commit 9410c3e

File tree

6 files changed

+161
-7
lines changed

6 files changed

+161
-7
lines changed

pkg/resource/log_group/hook.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
svcsdktypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
2323
"github.com/aws/aws-sdk-go/aws"
2424

25+
"github.com/aws-controllers-k8s/cloudwatchlogs-controller/pkg/sync"
2526
svcapitypes "github.com/aws-controllers-k8s/cloudwatchlogs-controller/apis/v1alpha1"
2627
)
2728

@@ -137,6 +138,8 @@ func (rm *resourceManager) removeSubscriptionFilter(
137138
return output, nil
138139
}
139140

141+
var getTags = sync.GetResourceTags
142+
140143
// customUpdateLogGroup patches each of the resource properties in the backend AWS
141144
// service API and returns a new resource with updated fields.
142145
func (rm *resourceManager) customUpdateLogGroup(
@@ -152,9 +155,15 @@ func (rm *resourceManager) customUpdateLogGroup(
152155
// Merge in the information we read from the API call above to the copy of
153156
// the original Kubernetes object we passed to the function
154157
ko := desired.ko.DeepCopy()
155-
158+
ko.Status = latest.ko.Status
156159
rm.setStatusDefaults(ko)
157160

161+
if delta.DifferentAt("Spec.Tags") {
162+
err = sync.SyncResourceTags(ctx, rm.sdkapi, rm.metrics, string(*latest.ko.Status.ACKResourceMetadata.ARN), desired.ko.Spec.Tags, latest.ko.Spec.Tags, convertToOrderedACKTags)
163+
if err != nil {
164+
return &resource{ko}, err
165+
}
166+
}
158167
if delta.DifferentAt("Spec.RetentionDays") {
159168
if err := rm.updateRetentionPeriod(ctx, desired); err != nil {
160169
return &resource{ko}, err

pkg/resource/log_group/sdk.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sync/tags.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package sync
15+
16+
import (
17+
"context"
18+
19+
ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare"
20+
ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log"
21+
acktags "github.com/aws-controllers-k8s/runtime/pkg/tags"
22+
svcsdk "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
23+
)
24+
25+
type metricsRecorder interface {
26+
RecordAPICall(opType string, opID string, err error)
27+
}
28+
29+
type tagsClient interface {
30+
TagResource(context.Context, *svcsdk.TagResourceInput, ...func(*svcsdk.Options)) (*svcsdk.TagResourceOutput, error)
31+
ListTagsForResource(context.Context, *svcsdk.ListTagsForResourceInput, ...func(*svcsdk.Options)) (*svcsdk.ListTagsForResourceOutput, error)
32+
UntagResource(context.Context, *svcsdk.UntagResourceInput, ...func(*svcsdk.Options)) (*svcsdk.UntagResourceOutput, error)
33+
}
34+
35+
// GetResourceTags retrieves a resource list of tags.
36+
func GetResourceTags(
37+
ctx context.Context,
38+
client tagsClient,
39+
mr metricsRecorder,
40+
resourceARN string,
41+
) (map[string]*string, error) {
42+
listTagsForResourceResponse, err := client.ListTagsForResource(
43+
ctx,
44+
&svcsdk.ListTagsForResourceInput{
45+
ResourceArn: &resourceARN,
46+
},
47+
)
48+
mr.RecordAPICall("GET", "ListTagsForResource", err)
49+
if err != nil {
50+
return nil, err
51+
}
52+
tags := map[string]*string{}
53+
for key, val := range listTagsForResourceResponse.Tags {
54+
tags[key] = &val
55+
}
56+
return tags, nil
57+
}
58+
59+
// SyncResourceTags uses TagResource and UntagResource API Calls to add, remove
60+
// and update resource tags.
61+
func SyncResourceTags(
62+
ctx context.Context,
63+
client tagsClient,
64+
mr metricsRecorder,
65+
resourceARN string,
66+
desiredTags map[string]*string,
67+
currentTags map[string]*string,
68+
convertToOrderedACKTags func(tags map[string]*string) (acktags.Tags, []string),
69+
) (err error) {
70+
rlog := ackrtlog.FromContext(ctx)
71+
exit := rlog.Trace("SyncResourceTags")
72+
defer func() { exit(err) }()
73+
74+
desiredACKTags, _ := convertToOrderedACKTags(desiredTags)
75+
currentACKTags, _ := convertToOrderedACKTags(currentTags)
76+
77+
added, _, toRemove := ackcompare.GetTagsDifference(currentACKTags, desiredACKTags)
78+
79+
for key := range toRemove {
80+
if _, ok := added[key]; ok {
81+
delete(toRemove, key)
82+
}
83+
}
84+
85+
var removed []string
86+
for key := range toRemove {
87+
removed = append(removed, key)
88+
}
89+
90+
if len(removed) > 0 {
91+
_, err = client.UntagResource(
92+
ctx,
93+
&svcsdk.UntagResourceInput{
94+
ResourceArn: &resourceARN,
95+
TagKeys: removed,
96+
},
97+
)
98+
mr.RecordAPICall("UPDATE", "UntagResource", err)
99+
if err != nil {
100+
return err
101+
}
102+
}
103+
104+
if len(added) > 0 {
105+
_, err = client.TagResource(
106+
ctx,
107+
&svcsdk.TagResourceInput{
108+
ResourceArn: &resourceARN,
109+
Tags: added,
110+
},
111+
)
112+
mr.RecordAPICall("UPDATE", "TagResource", err)
113+
if err != nil {
114+
return err
115+
}
116+
}
117+
return nil
118+
}

templates/hooks/log_group/sdk_read_many_post_set_output.go.tpl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@
44
ko.Spec.SubscriptionFilters, err = rm.getSubscriptionFilters(ctx, r.ko.Spec.Name)
55
if err != nil {
66
return nil, err
7-
}
7+
}
8+
ko.Spec.Tags, err = getTags(ctx, rm.sdkapi, rm.metrics, string(*ko.Status.ACKResourceMetadata.ARN))
9+
if err != nil {
10+
return nil, err
11+
}

test/e2e/log_group.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ def get_tags(log_group_arn):
103103
c = boto3.client('logs')
104104
try:
105105
resp = c.list_tags_for_resource(
106-
ResourceName=log_group_arn,
106+
resourceArn=log_group_arn,
107107
)
108-
return resp['TagList']
109-
except c.exceptions.LogGroupNotFoundFault:
108+
return resp['tags']
109+
except c.exceptions.ResourceNotFoundException:
110110
return None

test/e2e/tests/test_log_group.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import time
1818

1919
import pytest
20-
20+
from acktest import tags
2121
from acktest.k8s import resource as k8s
2222
from acktest.resources import random_suffix_name
2323
from e2e import service_marker, CRD_GROUP, CRD_VERSION, load_resource
@@ -87,19 +87,38 @@ def test_crud(self, _log_group):
8787
cr = k8s.get_resource(ref)
8888
assert 'creationTime' in cr['status']
8989
assert cr['status']['creationTime'] > 0
90+
assert 'ackResourceMetadata' in cr['status']
91+
assert 'arn' in cr['status']['ackResourceMetadata']
92+
arn = cr['status']['ackResourceMetadata']['arn']
9093

9194
# update retention period
9295
updated_retention = 3
9396
updates = {
9497
"spec": {
95-
"retentionDays": updated_retention
98+
"retentionDays": updated_retention,
99+
"tags": {
100+
"newKey": "newVal"
101+
}
96102
}
97103
}
98104

99105
k8s.patch_custom_resource(ref, updates)
100106
time.sleep(UPDATE_WAIT_AFTER_SECONDS)
101107

102108
assert log_group.exists_with_retention_period(log_group_name, updated_retention)
109+
cr = k8s.get_resource(ref)
110+
111+
latest_tags = log_group.get_tags(arn)
112+
desired_tags = cr['spec']['tags']
113+
tags.assert_ack_system_tags(
114+
tags=latest_tags,
115+
)
116+
tags.assert_equal_without_ack_tags(
117+
expected=desired_tags,
118+
actual=latest_tags,
119+
)
120+
121+
103122

104123
@pytest.mark.resource_data({'resource_file': 'invalid/log_group_invalid_parameter'})
105124
def test_terminal_condition_invalid_parameter(self, _log_group):

0 commit comments

Comments
 (0)