Skip to content

Commit 4f39a70

Browse files
UpdateTags support for SecurityGroup resource (#95)
Description of changes: - Implemented update tags support for **Security Group** resource. - Added e2e tests for the same. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent c5235cb commit 4f39a70

File tree

6 files changed

+219
-2
lines changed

6 files changed

+219
-2
lines changed

apis/v1alpha1/ack-generate-metadata.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
ack_generate_info:
2-
build_date: "2022-09-13T21:29:16Z"
2+
build_date: "2022-09-13T23:09:05Z"
33
build_hash: 585f06bbd6d4cc1b738acb85901e7a009bf452c7
44
go_version: go1.18.3
55
version: v0.20.0

pkg/resource/security_group/hook.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,105 @@ func (rm *resourceManager) syncSGRules(
160160
return nil
161161
}
162162

163+
// syncTags used to keep tags in sync by calling Create and Delete Tags API's
164+
func (rm *resourceManager) syncTags(
165+
ctx context.Context,
166+
desired *resource,
167+
latest *resource,
168+
) (err error) {
169+
rlog := ackrtlog.FromContext(ctx)
170+
exit := rlog.Trace("rm.syncTags")
171+
defer func(err error) {
172+
exit(err)
173+
}(err)
174+
175+
resourceId := []*string{latest.ko.Status.ID}
176+
177+
toAdd, toDelete := computeTagsDelta(
178+
desired.ko.Spec.Tags, latest.ko.Spec.Tags,
179+
)
180+
181+
if len(toDelete) > 0 {
182+
rlog.Debug("removing tags from security group resource", "tags", toDelete)
183+
_, err = rm.sdkapi.DeleteTagsWithContext(
184+
ctx,
185+
&svcsdk.DeleteTagsInput{
186+
Resources: resourceId,
187+
Tags: rm.sdkTags(toDelete),
188+
},
189+
)
190+
rm.metrics.RecordAPICall("UPDATE", "DeleteTags", err)
191+
if err != nil {
192+
return err
193+
}
194+
195+
}
196+
197+
if len(toAdd) > 0 {
198+
rlog.Debug("adding tags to security group resource", "tags", toAdd)
199+
_, err = rm.sdkapi.CreateTagsWithContext(
200+
ctx,
201+
&svcsdk.CreateTagsInput{
202+
Resources: resourceId,
203+
Tags: rm.sdkTags(toAdd),
204+
},
205+
)
206+
rm.metrics.RecordAPICall("UPDATE", "CreateTags", err)
207+
if err != nil {
208+
return err
209+
}
210+
}
211+
212+
return nil
213+
}
214+
215+
// sdkTags converts *svcapitypes.Tag array to a *svcsdk.Tag array
216+
func (rm *resourceManager) sdkTags(
217+
tags []*svcapitypes.Tag,
218+
) (sdktags []*svcsdk.Tag) {
219+
220+
for _, i := range tags {
221+
sdktag := rm.newTag(*i)
222+
sdktags = append(sdktags, sdktag)
223+
}
224+
225+
return sdktags
226+
}
227+
228+
// computeTagsDelta returns tags to be added and removed from the resource
229+
func computeTagsDelta(
230+
desired []*svcapitypes.Tag,
231+
latest []*svcapitypes.Tag,
232+
) (toAdd []*svcapitypes.Tag, toDelete []*svcapitypes.Tag) {
233+
234+
desiredTags := map[string]string{}
235+
for _, tag := range desired {
236+
desiredTags[*tag.Key] = *tag.Value
237+
}
238+
239+
latestTags := map[string]string{}
240+
for _, tag := range latest {
241+
latestTags[*tag.Key] = *tag.Value
242+
}
243+
244+
for _, tag := range desired {
245+
val, ok := latestTags[*tag.Key]
246+
if !ok || val != *tag.Value {
247+
toAdd = append(toAdd, tag)
248+
}
249+
}
250+
251+
for _, tag := range latest {
252+
_, ok := desiredTags[*tag.Key]
253+
if !ok {
254+
toDelete = append(toDelete, tag)
255+
}
256+
}
257+
258+
return toAdd, toDelete
259+
260+
}
261+
163262
// updateTagSpecificationsInCreateRequest adds
164263
// Tags defined in the Spec to CreateSecurityGroupInput.TagSpecification
165264
// and ensures the ResourceType is always set to 'security-group'
@@ -328,6 +427,12 @@ func (rm *resourceManager) customUpdateSecurityGroup(
328427
}
329428
}
330429

430+
if delta.DifferentAt("Spec.Tags") {
431+
if err := rm.syncTags(ctx, desired, latest); err != nil {
432+
return nil, err
433+
}
434+
}
435+
331436
return desired, nil
332437
}
333438

pkg/resource/security_group/sdk.go

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

templates/hooks/security_group/sdk_file_end.go.tpl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,30 @@ func (rm *resourceManager) new{{ $sgRuleRefName }}(
2828
return res
2929
}
3030

31+
{{/* Helper method for tag support */}}
32+
{{- range $specFieldName, $specField := $CRD.Config.Resources.SecurityGroup.Fields }}
33+
{{- if $specField.From }}
34+
{{- $operationName := $specField.From.Operation }}
35+
{{- $operation := (index $SDKAPI.API.Operations $operationName) -}}
36+
{{- range $securityGroupRefName, $securityGroupMemberRefs := $operation.InputRef.Shape.MemberRefs -}}
37+
{{- if eq $securityGroupRefName "Tags" }}
38+
{{- $securityGroupRef := $securityGroupMemberRefs.Shape.MemberRef }}
39+
{{- $securityGroupRefName = "Tag" }}
40+
41+
func (rm *resourceManager) new{{ $securityGroupRefName }}(
42+
c svcapitypes.{{ $securityGroupRefName }},
43+
) *svcsdk.{{ $securityGroupRefName }} {
44+
res := &svcsdk.{{ $securityGroupRefName }}{}
45+
{{ GoCodeSetSDKForStruct $CRD "" "res" $securityGroupRef "" "c" 1 }}
46+
return res
47+
}
48+
3149
{{- end }}
3250

51+
{{- end }}
52+
{{- end }}
53+
{{- end }}
54+
{{- end }}
3355
{{- end }}
3456
{{- end }}
3557
{{- end }}

test/e2e/resources/security_group.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@ metadata:
55
spec:
66
description: $SECURITY_GROUP_DESCRIPTION
77
name: $SECURITY_GROUP_NAME
8-
vpcID: $VPC_ID
8+
vpcID: $VPC_ID
9+
tags:
10+
- key: $TAG_KEY
11+
value: $TAG_VALUE

test/e2e/tests/test_security_group.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"""Integration tests for the SecurityGroup API.
1515
"""
1616

17+
import resource
1718
import pytest
1819
import time
1920
import logging
@@ -29,6 +30,7 @@
2930

3031
CREATE_WAIT_AFTER_SECONDS = 10
3132
DELETE_WAIT_AFTER_SECONDS = 10
33+
MODIFY_WAIT_AFTER_SECONDS = 5
3234

3335
@pytest.fixture
3436
def simple_security_group(request):
@@ -47,6 +49,10 @@ def simple_security_group(request):
4749
if 'resource_file' in data:
4850
resource_file = data['resource_file']
4951
replacements.update(data)
52+
if 'tag_key' in data:
53+
replacements["TAG_KEY"] = data["tag_key"]
54+
if 'tag_value' in data:
55+
replacements["TAG_VALUE"] = data["tag_value"]
5056

5157
# Load Security Group CR
5258
resource_data = load_ec2_resource(
@@ -186,4 +192,71 @@ def test_rules_create_update_delete(self, ec2_client, simple_security_group):
186192

187193
# Check Security Group no longer exists in AWS
188194
# Deleting Security Group will also delete rules
195+
ec2_validator.assert_security_group(resource_id, exists=False)
196+
197+
198+
@pytest.mark.resource_data({'tag_key': 'initialtagkey', 'tag_value': 'initialtagvalue'})
199+
def test_crud_tags(self, ec2_client, simple_security_group):
200+
(ref, cr) = simple_security_group
201+
202+
resource = k8s.get_resource(ref)
203+
resource_id = cr["status"]["id"]
204+
205+
time.sleep(CREATE_WAIT_AFTER_SECONDS)
206+
207+
# Check SecurityGroup exists in AWS
208+
ec2_validator = EC2Validator(ec2_client)
209+
ec2_validator.assert_security_group(resource_id)
210+
211+
# Check tags exist for SecurityGroup resource
212+
assert resource["spec"]["tags"][0]["key"] == "initialtagkey"
213+
assert resource["spec"]["tags"][0]["value"] == "initialtagvalue"
214+
215+
# New pair of tags
216+
new_tags = [
217+
{
218+
"key": "updatedtagkey",
219+
"value": "updatedtagvalue",
220+
}
221+
222+
]
223+
224+
# Patch the SecurityGroup, updating the tags with new pair
225+
updates = {
226+
"spec": {"tags": new_tags},
227+
}
228+
229+
k8s.patch_custom_resource(ref, updates)
230+
time.sleep(MODIFY_WAIT_AFTER_SECONDS)
231+
232+
# Check resource synced successfully
233+
assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5)
234+
235+
# Assert tags are updated for SecurityGroup resource
236+
resource = k8s.get_resource(ref)
237+
assert resource["spec"]["tags"][0]["key"] == "updatedtagkey"
238+
assert resource["spec"]["tags"][0]["value"] == "updatedtagvalue"
239+
240+
# Patch the SecurityGroup resource, deleting the tags
241+
updates = {
242+
"spec": {"tags": []},
243+
}
244+
245+
k8s.patch_custom_resource(ref, updates)
246+
time.sleep(MODIFY_WAIT_AFTER_SECONDS)
247+
248+
# Check resource synced successfully
249+
assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5)
250+
251+
# Assert tags are deleted
252+
resource = k8s.get_resource(ref)
253+
assert len(resource['spec']['tags']) == 0
254+
255+
# Delete k8s resource
256+
_, deleted = k8s.delete_custom_resource(ref)
257+
assert deleted is True
258+
259+
time.sleep(DELETE_WAIT_AFTER_SECONDS)
260+
261+
# Check SecurityGroup no longer exists in AWS
189262
ec2_validator.assert_security_group(resource_id, exists=False)

0 commit comments

Comments
 (0)