Skip to content

Commit 4743cc0

Browse files
Update tags support for NAT gateway (#102)
Description of changes: - Included `update_operation` in `generator.yaml` file. - Implemented update tags support for **NATGateway** 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 2e102f0 commit 4743cc0

File tree

8 files changed

+250
-10
lines changed

8 files changed

+250
-10
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-15T22:23:28Z"
2+
build_date: "2022-09-16T18:43:47Z"
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: ba7c8beee2b77dd73262cc870ec0a19ea7ca174f
10+
file_checksum: 8573a89b8220023263610579bfb84b762d99f5fa
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
@@ -297,6 +297,10 @@ resources:
297297
hooks:
298298
sdk_create_post_build_request:
299299
template_path: hooks/nat_gateway/sdk_create_post_build_request.go.tpl
300+
sdk_file_end:
301+
template_path: hooks/nat_gateway/sdk_file_end.go.tpl
302+
update_operation:
303+
custom_method_name: customUpdateNATGateway
300304
RouteTable:
301305
fields:
302306
# RouteStatuses as Route to ensure

generator.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,10 @@ resources:
297297
hooks:
298298
sdk_create_post_build_request:
299299
template_path: hooks/nat_gateway/sdk_create_post_build_request.go.tpl
300+
sdk_file_end:
301+
template_path: hooks/nat_gateway/sdk_file_end.go.tpl
302+
update_operation:
303+
custom_method_name: customUpdateNATGateway
300304
RouteTable:
301305
fields:
302306
# RouteStatuses as Route to ensure

pkg/resource/nat_gateway/hook.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,137 @@
1414
package nat_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) customUpdateNATGateway(
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.customUpdateNATGateway")
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.NATGatewayID}
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 NATGateway 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 NATGateway 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 CreateNatGatewayInput.TagSpecification
22150
// and ensures the ResourceType is always set to 'natgateway'

pkg/resource/nat_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 NAT Gateway */}}
5+
{{- range $specFieldName, $specField := $CRD.Config.Resources.NatGateway.Fields }}
6+
{{- if $specField.From }}
7+
{{- $operationName := $specField.From.Operation }}
8+
{{- $operation := (index $SDKAPI.API.Operations $operationName) -}}
9+
{{- range $natGatewayRefName, $natGatewayMemberRefs := $operation.InputRef.Shape.MemberRefs -}}
10+
{{- if eq $natGatewayRefName "Tags" }}
11+
{{- $natGatewayRef := $natGatewayMemberRefs.Shape.MemberRef }}
12+
{{- $natGatewayRefName = "Tag" }}
13+
func (rm *resourceManager) new{{ $natGatewayRefName }}(
14+
c svcapitypes.{{ $natGatewayRefName }},
15+
) *svcsdk.{{ $natGatewayRefName }} {
16+
res := &svcsdk.{{ $natGatewayRefName }}{}
17+
{{ GoCodeSetSDKForStruct $CRD "" "res" $natGatewayRef "" "c" 1 }}
18+
return res
19+
}
20+
{{- end }}
21+
{{- end }}
22+
{{- end }}
23+
{{- end }}

test/e2e/resources/nat_gateway.yaml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ metadata:
55
spec:
66
allocationID: $ALLOCATION_ID
77
subnetID: $SUBNET_ID
8-
tagSpecifications:
9-
- resourceType: "natgateway"
10-
tags:
11-
- key: Name
12-
value: $NAT_GATEWAY_NAME
8+
tags:
9+
- key: $TAG_KEY
10+
value: $TAG_VALUE

test/e2e/tests/test_nat_gateway.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
CREATE_WAIT_AFTER_SECONDS = 10
3131
DELETE_WAIT_AFTER_SECONDS = 10
32+
MODIFY_WAIT_AFTER_SECONDS = 10
3233

3334
@pytest.fixture
3435
def standard_elastic_address():
@@ -69,7 +70,7 @@ def standard_elastic_address():
6970
pass
7071

7172
@pytest.fixture
72-
def simple_nat_gateway(standard_elastic_address):
73+
def simple_nat_gateway(standard_elastic_address, request):
7374
test_resource_values = REPLACEMENT_VALUES.copy()
7475
resource_name = random_suffix_name("nat-gateway-test", 24)
7576
test_vpc = get_bootstrap_resources().SharedTestVPC
@@ -81,6 +82,14 @@ def simple_nat_gateway(standard_elastic_address):
8182
test_resource_values["SUBNET_ID"] = subnet_id
8283
test_resource_values["ALLOCATION_ID"] = eip["status"]["allocationID"]
8384

85+
marker = request.node.get_closest_marker("resource_data")
86+
if marker is not None:
87+
data = marker.args[0]
88+
if 'tag_key' in data:
89+
test_resource_values["TAG_KEY"] = data["tag_key"]
90+
if 'tag_value' in data:
91+
test_resource_values["TAG_VALUE"] = data["tag_value"]
92+
8493
# Load NAT Gateway CR
8594
resource_data = load_ec2_resource(
8695
"nat_gateway",
@@ -133,6 +142,67 @@ def test_create_delete(self, simple_nat_gateway, ec2_client):
133142

134143
# Check NAT Gateway no longer exists in AWS
135144
ec2_validator.assert_nat_gateway(resource_id, exists=False)
145+
146+
@pytest.mark.resource_data({'tag_key': 'initialtagkey', 'tag_value': 'initialtagvalue'})
147+
def test_crud_tags(self, ec2_client, simple_nat_gateway):
148+
(ref, cr) = simple_nat_gateway
149+
150+
resource = k8s.get_resource(ref)
151+
resource_id = cr["status"]["natGatewayID"]
152+
153+
time.sleep(CREATE_WAIT_AFTER_SECONDS)
154+
155+
# Check natGateway exists in AWS
156+
ec2_validator = EC2Validator(ec2_client)
157+
ec2_validator.assert_nat_gateway(resource_id)
158+
159+
# Check tags exist for natGateway resource
160+
assert resource["spec"]["tags"][0]["key"] == "initialtagkey"
161+
assert resource["spec"]["tags"][0]["value"] == "initialtagvalue"
162+
163+
# New pair of tags
164+
new_tags = [
165+
{
166+
"key": "updatedtagkey",
167+
"value": "updatedtagvalue",
168+
}
169+
170+
]
171+
172+
# Patch the natGateway, updating the tags with new pair
173+
updates = {
174+
"spec": {"tags": new_tags},
175+
}
176+
177+
k8s.patch_custom_resource(ref, updates)
178+
time.sleep(MODIFY_WAIT_AFTER_SECONDS)
179+
180+
# Assert tags are updated for natGateway resource
181+
resource = k8s.get_resource(ref)
182+
assert resource["spec"]["tags"][0]["key"] == "updatedtagkey"
183+
assert resource["spec"]["tags"][0]["value"] == "updatedtagvalue"
184+
185+
# Patch the natGateway resource, deleting the tags
186+
updates = {
187+
"spec": {"tags": []},
188+
}
189+
190+
k8s.patch_custom_resource(ref, updates)
191+
time.sleep(MODIFY_WAIT_AFTER_SECONDS)
192+
193+
# Assert tags are deleted
194+
resource = k8s.get_resource(ref)
195+
assert len(resource['spec']['tags']) == 0
196+
197+
# Delete k8s resource
198+
_, deleted = k8s.delete_custom_resource(ref)
199+
assert deleted is True
200+
201+
time.sleep(DELETE_WAIT_AFTER_SECONDS)
202+
203+
# Check natGateway no longer exists in AWS
204+
ec2_validator.assert_nat_gateway(resource_id, exists=False)
205+
136206

137207
def test_terminal_condition_invalid_subnet(self, standard_elastic_address):
138208
test_resource_values = REPLACEMENT_VALUES.copy()

0 commit comments

Comments
 (0)