Skip to content

Commit 2e102f0

Browse files
Update tags support for Instance resource (#100)
Description of changes: - Included `update_operation` in `generator.yaml` file. - Implemented update tags support for **Instance** 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 8b0543a commit 2e102f0

File tree

7 files changed

+243
-4
lines changed

7 files changed

+243
-4
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-15T18:20:58Z"
2+
build_date: "2022-09-15T22:23:28Z"
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: 765a0d09844f3b22d5d0b2d24f39fc0eec244d5c
10+
file_checksum: ba7c8beee2b77dd73262cc870ec0a19ea7ca174f
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
@@ -207,6 +207,10 @@ resources:
207207
template_path: hooks/instance/sdk_create_post_build_request.go.tpl
208208
sdk_delete_post_build_request:
209209
template_path: hooks/instance/sdk_delete_post_build_request.go.tpl
210+
sdk_file_end:
211+
template_path: hooks/instance/sdk_file_end.go.tpl
212+
update_operation:
213+
custom_method_name: customUpdateInstance
210214
ElasticIPAddress:
211215
exceptions:
212216
terminal_codes:

generator.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ resources:
207207
template_path: hooks/instance/sdk_create_post_build_request.go.tpl
208208
sdk_delete_post_build_request:
209209
template_path: hooks/instance/sdk_delete_post_build_request.go.tpl
210+
sdk_file_end:
211+
template_path: hooks/instance/sdk_file_end.go.tpl
212+
update_operation:
213+
custom_method_name: customUpdateInstance
210214
ElasticIPAddress:
211215
exceptions:
212216
terminal_codes:

pkg/resource/instance/hooks.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414
package instance
1515

1616
import (
17+
"context"
1718
"errors"
1819

20+
svcapitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1"
21+
ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare"
22+
ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log"
1923
svcsdk "github.com/aws/aws-sdk-go/service/ec2"
2024
)
2125

@@ -31,6 +35,129 @@ func addInstanceIDsToTerminateRequest(r *resource,
3135
return nil
3236
}
3337

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

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

test/e2e/tests/test_instance.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
CREATE_WAIT_AFTER_SECONDS = 10
3636
DELETE_WAIT_AFTER_SECONDS = 10
37+
MODIFY_WAIT_AFTER_SECONDS = 5
3738
TIMEOUT_SECONDS = 300
3839

3940
def get_instance(ec2_client, instance_id: str) -> dict:
@@ -159,6 +160,73 @@ def test_create_delete(self, ec2_client, instance):
159160
_, deleted = k8s.delete_custom_resource(ref, 2, 5)
160161
assert deleted is True
161162

163+
# Reservation still exists, but instance will commence termination
164+
# State needs to be 'terminated' in order to remove the dependency on the shared subnet
165+
# for successful test cleanup
166+
wait_for_instance_or_die(ec2_client, resource_id, 'terminated', TIMEOUT_SECONDS)
167+
168+
def test_crud_tags(self, ec2_client, instance):
169+
(ref, cr) = instance
170+
171+
resource = k8s.get_resource(ref)
172+
resource_id = cr["status"]["instanceID"]
173+
174+
time.sleep(CREATE_WAIT_AFTER_SECONDS)
175+
176+
# Check Instance exists
177+
instance = get_instance(ec2_client, resource_id)
178+
assert instance is not None
179+
180+
# 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
183+
184+
# New pair of tags
185+
new_tags = [
186+
{
187+
"key": "updatedtagkey",
188+
"value": "updatedtagvalue",
189+
}
190+
191+
]
192+
193+
# Patch the Instance, 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 Instance resource
205+
resource = k8s.get_resource(ref)
206+
assert resource["spec"]["tags"][0]["key"] == "updatedtagkey"
207+
assert resource["spec"]["tags"][0]["value"] == "updatedtagvalue"
208+
209+
# Patch the Instance resource, deleting the tags
210+
updates = {
211+
"spec": {"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 len(resource['spec']['tags']) == 0
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+
162230
# Reservation still exists, but instance will commence termination
163231
# State needs to be 'terminated' in order to remove the dependency on the shared subnet
164232
# for successful test cleanup

0 commit comments

Comments
 (0)