Skip to content

Commit 116f911

Browse files
Update tags support for RouteTable resource (#103)
Description of changes: - Implemented `updateTags` support for **RouteTable** resource. - Added e2e tests for update tags. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 4743cc0 commit 116f911

File tree

7 files changed

+260
-16
lines changed

7 files changed

+260
-16
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-16T18:43:47Z"
2+
build_date: "2022-09-20T18:05:13Z"
33
build_hash: 1da44f4be322eea156ed23a86e33c29da5cede9c
44
go_version: go1.18.3
55
version: v0.20.1-2-g1da44f4

pkg/resource/route_table/hooks.go

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,19 @@ func (rm *resourceManager) customUpdateRouteTable(
198198
if err := rm.syncRoutes(ctx, desired, latest); err != nil {
199199
return nil, err
200200
}
201-
latest, err = rm.sdkFind(ctx, latest)
202-
if err != nil {
201+
}
202+
203+
if delta.DifferentAt("Spec.Tags") {
204+
if err := rm.syncTags(ctx, desired, latest); err != nil {
203205
return nil, err
204206
}
205207
}
206208

209+
latest, err = rm.sdkFind(ctx, latest)
210+
if err != nil {
211+
return nil, err
212+
}
213+
207214
return latest, nil
208215
}
209216

@@ -259,6 +266,105 @@ func removeLocalRoute(
259266
return ret
260267
}
261268

269+
// syncTags used to keep tags in sync by calling Create and Delete Tags API's
270+
func (rm *resourceManager) syncTags(
271+
ctx context.Context,
272+
desired *resource,
273+
latest *resource,
274+
) (err error) {
275+
rlog := ackrtlog.FromContext(ctx)
276+
exit := rlog.Trace("rm.syncTags")
277+
defer func(err error) {
278+
exit(err)
279+
}(err)
280+
281+
resourceId := []*string{latest.ko.Status.RouteTableID}
282+
283+
toAdd, toDelete := computeTagsDelta(
284+
desired.ko.Spec.Tags, latest.ko.Spec.Tags,
285+
)
286+
287+
if len(toDelete) > 0 {
288+
rlog.Debug("removing tags from route table resource", "tags", toDelete)
289+
_, err = rm.sdkapi.DeleteTagsWithContext(
290+
ctx,
291+
&svcsdk.DeleteTagsInput{
292+
Resources: resourceId,
293+
Tags: rm.sdkTags(toDelete),
294+
},
295+
)
296+
rm.metrics.RecordAPICall("UPDATE", "DeleteTags", err)
297+
if err != nil {
298+
return err
299+
}
300+
301+
}
302+
303+
if len(toAdd) > 0 {
304+
rlog.Debug("adding tags to route table resource", "tags", toAdd)
305+
_, err = rm.sdkapi.CreateTagsWithContext(
306+
ctx,
307+
&svcsdk.CreateTagsInput{
308+
Resources: resourceId,
309+
Tags: rm.sdkTags(toAdd),
310+
},
311+
)
312+
rm.metrics.RecordAPICall("UPDATE", "CreateTags", err)
313+
if err != nil {
314+
return err
315+
}
316+
}
317+
318+
return nil
319+
}
320+
321+
// sdkTags converts *svcapitypes.Tag array to a *svcsdk.Tag array
322+
func (rm *resourceManager) sdkTags(
323+
tags []*svcapitypes.Tag,
324+
) (sdktags []*svcsdk.Tag) {
325+
326+
for _, i := range tags {
327+
sdktag := rm.newTag(*i)
328+
sdktags = append(sdktags, sdktag)
329+
}
330+
331+
return sdktags
332+
}
333+
334+
// computeTagsDelta returns tags to be added and removed from the resource
335+
func computeTagsDelta(
336+
desired []*svcapitypes.Tag,
337+
latest []*svcapitypes.Tag,
338+
) (toAdd []*svcapitypes.Tag, toDelete []*svcapitypes.Tag) {
339+
340+
desiredTags := map[string]string{}
341+
for _, tag := range desired {
342+
desiredTags[*tag.Key] = *tag.Value
343+
}
344+
345+
latestTags := map[string]string{}
346+
for _, tag := range latest {
347+
latestTags[*tag.Key] = *tag.Value
348+
}
349+
350+
for _, tag := range desired {
351+
val, ok := latestTags[*tag.Key]
352+
if !ok || val != *tag.Value {
353+
toAdd = append(toAdd, tag)
354+
}
355+
}
356+
357+
for _, tag := range latest {
358+
_, ok := desiredTags[*tag.Key]
359+
if !ok {
360+
toDelete = append(toDelete, tag)
361+
}
362+
}
363+
364+
return toAdd, toDelete
365+
366+
}
367+
262368
// updateTagSpecificationsInCreateRequest adds
263369
// Tags defined in the Spec to CreateRouteTableInput.TagSpecification
264370
// and ensures the ResourceType is always set to 'route-table'

pkg/resource/route_table/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/route_table/sdk_file_end.go.tpl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,30 @@ func (rm *resourceManager) new{{ $memberRefName }}(
3030
return res
3131
}
3232

33+
{{/* Helper method for tag support */}}
34+
{{- range $specFieldName, $specField := $CRD.Config.Resources.RouteTable.Fields }}
35+
{{- if $specField.From }}
36+
{{- $operationName := $specField.From.Operation }}
37+
{{- $operation := (index $SDKAPI.API.Operations $operationName) -}}
38+
{{- range $rtRefName, $rtMemberRefs := $operation.InputRef.Shape.MemberRefs -}}
39+
{{- if eq $rtRefName "Tags" }}
40+
{{- $rtRef := $rtMemberRefs.Shape.MemberRef }}
41+
{{- $rtRefName = "Tag" }}
42+
43+
func (rm *resourceManager) new{{ $rtRefName }}(
44+
c svcapitypes.{{ $rtRefName }},
45+
) *svcsdk.{{ $rtRefName }} {
46+
res := &svcsdk.{{ $rtRefName }}{}
47+
{{ GoCodeSetSDKForStruct $CRD "" "res" $rtRef "" "c" 1 }}
48+
return res
49+
}
50+
51+
{{- end }}
52+
53+
{{- end }}
54+
{{- end }}
55+
{{- end }}
56+
3357
{{- end }}
3458
{{- end }}
3559
{{- end }}

test/e2e/resources/route_table.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ spec:
66
routes:
77
- destinationCIDRBlock: $DEST_CIDR_BLOCK
88
gatewayID: $IGW_ID
9-
vpcID: $VPC_ID
9+
vpcID: $VPC_ID
10+
tags:
11+
- key: $TAG_KEY
12+
value: $TAG_VALUE

test/e2e/tests/test_instance.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,17 @@ def get_ami_id(ec2_client):
8888
except Exception as e:
8989
logging.debug(e)
9090

91+
def contains_tag(resource, tag):
92+
try:
93+
tag_key, tag_val = tag.popitem()
94+
for t in resource["spec"]["tags"]:
95+
if t["key"] == tag_key and t["value"] == tag_val:
96+
return True
97+
except:
98+
pass
99+
100+
return False
101+
91102

92103
@pytest.fixture
93104
def instance(ec2_client):
@@ -178,17 +189,17 @@ def test_crud_tags(self, ec2_client, instance):
178189
assert instance is not None
179190

180191
# Check tags exist for Instance resource
181-
assert resource["spec"]["tags"][0]["key"] == INSTANCE_TAG_KEY
182-
assert resource["spec"]["tags"][0]["value"] == INSTANCE_TAG_VAL
192+
assert contains_tag(resource, {INSTANCE_TAG_KEY: INSTANCE_TAG_VAL})
193+
194+
# Fetch all tags including ACK tags applied by default
195+
new_tags = resource['spec']['tags']
183196

184197
# New pair of tags
185-
new_tags = [
186-
{
198+
new_tag = {
187199
"key": "updatedtagkey",
188200
"value": "updatedtagvalue",
189-
}
190-
191-
]
201+
}
202+
new_tags.append(new_tag)
192203

193204
# Patch the Instance, updating the tags with new pair
194205
updates = {
@@ -203,12 +214,12 @@ def test_crud_tags(self, ec2_client, instance):
203214

204215
# Assert tags are updated for Instance resource
205216
resource = k8s.get_resource(ref)
206-
assert resource["spec"]["tags"][0]["key"] == "updatedtagkey"
207-
assert resource["spec"]["tags"][0]["value"] == "updatedtagvalue"
217+
assert contains_tag(resource, {"updatedtagkey": "updatedtagvalue"})
208218

219+
new_tags = []
209220
# Patch the Instance resource, deleting the tags
210221
updates = {
211-
"spec": {"tags": []},
222+
"spec": {"tags": new_tags},
212223
}
213224

214225
k8s.patch_custom_resource(ref, updates)
@@ -219,7 +230,7 @@ def test_crud_tags(self, ec2_client, instance):
219230

220231
# Assert tags are deleted
221232
resource = k8s.get_resource(ref)
222-
assert len(resource['spec']['tags']) == 0
233+
assert contains_tag(resource, {"updatedtagkey": "updatedtagvalue"}) is False
223234

224235
# Delete k8s resource
225236
_, deleted = k8s.delete_custom_resource(ref)

test/e2e/tests/test_route_table.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,21 @@
3030
DEFAULT_WAIT_AFTER_SECONDS = 5
3131
CREATE_WAIT_AFTER_SECONDS = 10
3232
DELETE_WAIT_AFTER_SECONDS = 10
33+
MODIFY_WAIT_AFTER_SECONDS = 10
34+
35+
def contains_tag(resource, tag):
36+
try:
37+
tag_key, tag_val = tag.popitem()
38+
for t in resource["spec"]["tags"]:
39+
if t["key"] == tag_key and t["value"] == tag_val:
40+
return True
41+
except:
42+
pass
43+
44+
return False
3345

3446
@pytest.fixture
35-
def simple_route_table():
47+
def simple_route_table(request):
3648
replacements = REPLACEMENT_VALUES.copy()
3749
resource_name = random_suffix_name("route-table-test", 24)
3850
test_vpc = get_bootstrap_resources().SharedTestVPC
@@ -45,6 +57,14 @@ def simple_route_table():
4557
replacements["IGW_ID"] = igw_id
4658
replacements["DEST_CIDR_BLOCK"] = test_cidr_block
4759

60+
marker = request.node.get_closest_marker("resource_data")
61+
if marker is not None:
62+
data = marker.args[0]
63+
if 'tag_key' in data:
64+
replacements["TAG_KEY"] = data["tag_key"]
65+
if 'tag_value' in data:
66+
replacements["TAG_VALUE"] = data["tag_value"]
67+
4868
# Load RouteTable CR
4969
resource_data = load_ec2_resource(
5070
"route_table",
@@ -141,5 +161,71 @@ def test_crud_route(self, ec2_client, simple_route_table):
141161

142162
time.sleep(DELETE_WAIT_AFTER_SECONDS)
143163

164+
# Check Route Table no longer exists in AWS
165+
ec2_validator.assert_route_table(resource_id, exists=False)
166+
167+
@pytest.mark.resource_data({'tag_key': 'initialtagkey', 'tag_value': 'initialtagvalue'})
168+
def test_crud_tags(self, ec2_client, simple_route_table):
169+
(ref, cr) = simple_route_table
170+
171+
resource = k8s.get_resource(ref)
172+
resource_id = cr["status"]["routeTableID"]
173+
174+
time.sleep(CREATE_WAIT_AFTER_SECONDS)
175+
176+
# Check Route Table exists in AWS
177+
ec2_validator = EC2Validator(ec2_client)
178+
ec2_validator.assert_route_table(resource_id)
179+
180+
# Check tags exist for Route Table resource
181+
assert contains_tag(resource, {"initialtagkey": "initialtagvalue"})
182+
183+
# Fetch all tags including ACK tags applied by default
184+
new_tags = resource['spec']['tags']
185+
186+
# New tag
187+
new_tag = {
188+
"key": "updatedtagkey",
189+
"value": "updatedtagvalue",
190+
}
191+
new_tags.append(new_tag)
192+
193+
# Patch the Route Table, updating the tags with new pair
194+
updates = {
195+
"spec": {"tags": new_tags},
196+
}
197+
198+
k8s.patch_custom_resource(ref, updates)
199+
time.sleep(MODIFY_WAIT_AFTER_SECONDS)
200+
201+
# Check resource synced successfully
202+
assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5)
203+
204+
# Assert tags are updated for Route Table resource
205+
resource = k8s.get_resource(ref)
206+
assert contains_tag(resource, {"updatedtagkey": "updatedtagvalue"})
207+
208+
new_tags = []
209+
# Patch the Route Table resource, deleting the tags
210+
updates = {
211+
"spec": {"tags": new_tags},
212+
}
213+
214+
k8s.patch_custom_resource(ref, updates)
215+
time.sleep(MODIFY_WAIT_AFTER_SECONDS)
216+
217+
# Check resource synced successfully
218+
assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5)
219+
220+
# Assert tags are deleted
221+
resource = k8s.get_resource(ref)
222+
assert contains_tag(resource, {"updatedtagkey": "updatedtagvalue"}) is False
223+
224+
# Delete k8s resource
225+
_, deleted = k8s.delete_custom_resource(ref)
226+
assert deleted is True
227+
228+
time.sleep(DELETE_WAIT_AFTER_SECONDS)
229+
144230
# Check Route Table no longer exists in AWS
145231
ec2_validator.assert_route_table(resource_id, exists=False)

0 commit comments

Comments
 (0)