Skip to content

Commit 8b0543a

Browse files
Update tags support for Transit Gateway resource (#99)
Description of changes: - Included `update_operation` in `generator.yaml` file. - Implemented update tags support for **Transit Gateway** resource. - Added integration 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 48b29c4 commit 8b0543a

File tree

8 files changed

+257
-7
lines changed

8 files changed

+257
-7
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
ack_generate_info:
2-
build_date: "2022-09-15T16:27:05Z"
2+
build_date: "2022-09-15T18:20:58Z"
33
build_hash: 1da44f4be322eea156ed23a86e33c29da5cede9c
44
go_version: go1.18.3
55
version: v0.20.1-2-g1da44f4
66
api_directory_checksum: 127aa0f51fbcdde596b8f42f93bcdf3b6dee8488
77
api_version: v1alpha1
88
aws_sdk_go_version: v1.44.93
99
generator_config_info:
10-
file_checksum: 42f9161f3b7c319d753fd54b681eecd9916d67f6
10+
file_checksum: 765a0d09844f3b22d5d0b2d24f39fc0eec244d5c
1111
original_file_name: generator.yaml
1212
last_modification:
1313
reason: API generation

apis/v1alpha1/generator.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,10 @@ resources:
435435
hooks:
436436
sdk_create_post_build_request:
437437
template_path: hooks/transit_gateway/sdk_create_post_build_request.go.tpl
438+
sdk_file_end:
439+
template_path: hooks/transit_gateway/sdk_file_end.go.tpl
440+
update_operation:
441+
custom_method_name: customUpdateTransitGateway
438442
Vpc:
439443
update_operation:
440444
custom_method_name: customUpdate

generator.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,10 @@ resources:
435435
hooks:
436436
sdk_create_post_build_request:
437437
template_path: hooks/transit_gateway/sdk_create_post_build_request.go.tpl
438+
sdk_file_end:
439+
template_path: hooks/transit_gateway/sdk_file_end.go.tpl
440+
update_operation:
441+
custom_method_name: customUpdateTransitGateway
438442
Vpc:
439443
update_operation:
440444
custom_method_name: customUpdate

pkg/resource/transit_gateway/hook.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,137 @@
1414
package transit_gateway
1515

1616
import (
17+
"context"
18+
19+
svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1"
20+
ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare"
21+
ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log"
1722
svcsdk "github.com/aws/aws-sdk-go/service/ec2"
1823
)
1924

25+
func (rm *resourceManager) customUpdateTransitGateway(
26+
ctx context.Context,
27+
desired *resource,
28+
latest *resource,
29+
delta *ackcompare.Delta,
30+
) (updated *resource, err error) {
31+
rlog := ackrtlog.FromContext(ctx)
32+
exit := rlog.Trace("rm.customUpdateTransitGateway")
33+
defer exit(err)
34+
35+
// Merge in the information we read from the API call above to the copy of
36+
// the original Kubernetes object we passed to the function
37+
ko := desired.ko.DeepCopy()
38+
39+
if delta.DifferentAt("Spec.Tags") {
40+
if err := rm.syncTags(ctx, desired, latest); err != nil {
41+
return nil, err
42+
}
43+
}
44+
45+
rm.setStatusDefaults(ko)
46+
return &resource{ko}, nil
47+
}
48+
49+
// syncTags used to keep tags in sync by calling Create and Delete API's
50+
func (rm *resourceManager) syncTags(
51+
ctx context.Context,
52+
desired *resource,
53+
latest *resource,
54+
) (err error) {
55+
rlog := ackrtlog.FromContext(ctx)
56+
exit := rlog.Trace("rm.syncTags")
57+
defer func(err error) {
58+
exit(err)
59+
}(err)
60+
61+
resourceId := []*string{latest.ko.Status.TransitGatewayID}
62+
63+
toAdd, toDelete := computeTagsDelta(
64+
desired.ko.Spec.Tags, latest.ko.Spec.Tags,
65+
)
66+
67+
if len(toDelete) > 0 {
68+
rlog.Debug("removing tags from TransitGateway resource", "tags", toDelete)
69+
_, err = rm.sdkapi.DeleteTagsWithContext(
70+
ctx,
71+
&svcsdk.DeleteTagsInput{
72+
Resources: resourceId,
73+
Tags: rm.sdkTags(toDelete),
74+
},
75+
)
76+
rm.metrics.RecordAPICall("UPDATE", "DeleteTags", err)
77+
if err != nil {
78+
return err
79+
}
80+
81+
}
82+
83+
if len(toAdd) > 0 {
84+
rlog.Debug("adding tags to TransitGateway resource", "tags", toAdd)
85+
_, err = rm.sdkapi.CreateTagsWithContext(
86+
ctx,
87+
&svcsdk.CreateTagsInput{
88+
Resources: resourceId,
89+
Tags: rm.sdkTags(toAdd),
90+
},
91+
)
92+
rm.metrics.RecordAPICall("UPDATE", "CreateTags", err)
93+
if err != nil {
94+
return err
95+
}
96+
}
97+
98+
return nil
99+
}
100+
101+
// sdkTags converts *svcapitypes.Tag array to a *svcsdk.Tag array
102+
func (rm *resourceManager) sdkTags(
103+
tags []*svcapitypes.Tag,
104+
) (sdktags []*svcsdk.Tag) {
105+
106+
for _, i := range tags {
107+
sdktag := rm.newTag(*i)
108+
sdktags = append(sdktags, sdktag)
109+
}
110+
111+
return sdktags
112+
}
113+
114+
// computeTagsDelta returns tags to be added and removed from the resource
115+
func computeTagsDelta(
116+
desired []*svcapitypes.Tag,
117+
latest []*svcapitypes.Tag,
118+
) (toAdd []*svcapitypes.Tag, toDelete []*svcapitypes.Tag) {
119+
120+
desiredTags := map[string]string{}
121+
for _, tag := range desired {
122+
desiredTags[*tag.Key] = *tag.Value
123+
}
124+
125+
latestTags := map[string]string{}
126+
for _, tag := range latest {
127+
latestTags[*tag.Key] = *tag.Value
128+
}
129+
130+
for _, tag := range desired {
131+
val, ok := latestTags[*tag.Key]
132+
if !ok || val != *tag.Value {
133+
toAdd = append(toAdd, tag)
134+
}
135+
}
136+
137+
for _, tag := range latest {
138+
_, ok := desiredTags[*tag.Key]
139+
if !ok {
140+
toDelete = append(toDelete, tag)
141+
}
142+
}
143+
144+
return toAdd, toDelete
145+
146+
}
147+
20148
// updateTagSpecificationsInCreateRequest adds
21149
// Tags defined in the Spec to CreateTransitGatewayInput.TagSpecification
22150
// and ensures the ResourceType is always set to 'transit-gateway'

pkg/resource/transit_gateway/sdk.go

Lines changed: 15 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{{ $CRD := .CRD }}
2+
{{ $SDKAPI := .SDKAPI }}
3+
4+
{{/* Generate helper methods for Transit Gateway */}}
5+
{{- range $specFieldName, $specField := $CRD.Config.Resources.TransitGateway.Fields }}
6+
{{- if $specField.From }}
7+
{{- $operationName := $specField.From.Operation }}
8+
{{- $operation := (index $SDKAPI.API.Operations $operationName) -}}
9+
{{- range $transitGatewayRefName, $transitGatewayMemberRefs := $operation.InputRef.Shape.MemberRefs -}}
10+
{{- if eq $transitGatewayRefName "Tags" }}
11+
{{- $transitGatewayRef := $transitGatewayMemberRefs.Shape.MemberRef }}
12+
{{- $transitGatewayRefName = "Tag" }}
13+
func (rm *resourceManager) new{{ $transitGatewayRefName }}(
14+
c svcapitypes.{{ $transitGatewayRefName }},
15+
) *svcsdk.{{ $transitGatewayRefName }} {
16+
res := &svcsdk.{{ $transitGatewayRefName }}{}
17+
{{ GoCodeSetSDKForStruct $CRD "" "res" $transitGatewayRef "" "c" 1 }}
18+
return res
19+
}
20+
{{- end }}
21+
{{- end }}
22+
{{- end }}
23+
{{- end }}

test/e2e/resources/transitgateway.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ apiVersion: ec2.services.k8s.aws/v1alpha1
22
kind: TransitGateway
33
metadata:
44
name: $TGW_NAME
5-
spec: {}
5+
spec:
6+
tags:
7+
- key: $TAG_KEY
8+
value: $TAG_VALUE

test/e2e/tests/test_transit_gateway.py

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,22 @@
3131
## TGWs are unable to be deleted while in "pending"
3232
CREATE_WAIT_AFTER_SECONDS = 90
3333
DELETE_WAIT_AFTER_SECONDS = 10
34+
MODIFY_WAIT_AFTER_SECONDS = 5
3435

3536
@pytest.fixture
36-
def simple_transit_gateway():
37+
def simple_transit_gateway(request):
3738
resource_name = random_suffix_name("tgw-ack-test", 24)
3839
replacements = REPLACEMENT_VALUES.copy()
3940
replacements["TGW_NAME"] = resource_name
4041

42+
marker = request.node.get_closest_marker("resource_data")
43+
if marker is not None:
44+
data = marker.args[0]
45+
if 'tag_key' in data:
46+
replacements["TAG_KEY"] = data['tag_key']
47+
if 'tag_value' in data:
48+
replacements["TAG_VALUE"] = data['tag_value']
49+
4150
# Load TGW CR
4251
resource_data = load_ec2_resource(
4352
"transitgateway",
@@ -87,4 +96,70 @@ def test_create_delete(self, ec2_client, simple_transit_gateway):
8796
time.sleep(DELETE_WAIT_AFTER_SECONDS)
8897

8998
# Check TGW no longer exists in AWS
90-
ec2_validator.assert_transit_gateway(resource_id, exists=False)
99+
ec2_validator.assert_transit_gateway(resource_id, exists=False)
100+
101+
@pytest.mark.resource_data({'tag_key': 'initialtagkey', 'tag_value': 'initialtagvalue'})
102+
def test_crud_tags(self, ec2_client, simple_transit_gateway):
103+
(ref, cr) = simple_transit_gateway
104+
105+
resource = k8s.get_resource(ref)
106+
resource_id = cr["status"]["transitGatewayID"]
107+
108+
time.sleep(CREATE_WAIT_AFTER_SECONDS)
109+
110+
# Check TransitGateway exists in AWS
111+
ec2_validator = EC2Validator(ec2_client)
112+
ec2_validator.assert_transit_gateway(resource_id)
113+
114+
# Check tags exist for TransitGateway resource
115+
assert resource["spec"]["tags"][0]["key"] == "initialtagkey"
116+
assert resource["spec"]["tags"][0]["value"] == "initialtagvalue"
117+
118+
# New pair of tags
119+
new_tags = [
120+
{
121+
"key": "updatedtagkey",
122+
"value": "updatedtagvalue",
123+
}
124+
125+
]
126+
127+
# Patch the TransitGateway, updating the tags with new pair
128+
updates = {
129+
"spec": {"tags": new_tags},
130+
}
131+
132+
k8s.patch_custom_resource(ref, updates)
133+
time.sleep(MODIFY_WAIT_AFTER_SECONDS)
134+
135+
# Check resource synced successfully
136+
assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5)
137+
138+
# Assert tags are updated for TransitGateway resource
139+
resource = k8s.get_resource(ref)
140+
assert resource["spec"]["tags"][0]["key"] == "updatedtagkey"
141+
assert resource["spec"]["tags"][0]["value"] == "updatedtagvalue"
142+
143+
# Patch the TransitGateway resource, deleting the tags
144+
updates = {
145+
"spec": {"tags": []},
146+
}
147+
148+
k8s.patch_custom_resource(ref, updates)
149+
time.sleep(MODIFY_WAIT_AFTER_SECONDS)
150+
151+
# Check resource synced successfully
152+
assert k8s.wait_on_condition(ref, "ACK.ResourceSynced", "True", wait_periods=5)
153+
154+
# Assert tags are deleted
155+
resource = k8s.get_resource(ref)
156+
assert len(resource['spec']['tags']) == 0
157+
158+
# Delete k8s resource
159+
_, deleted = k8s.delete_custom_resource(ref)
160+
assert deleted is True
161+
162+
time.sleep(DELETE_WAIT_AFTER_SECONDS)
163+
164+
# Check TransitGateway no longer exists in AWS
165+
ec2_validator.assert_transit_gateway(resource_id, exists=False)

0 commit comments

Comments
 (0)