Skip to content

Commit 99eedf9

Browse files
Update tags support for VpcEndpoint resource (#96)
Description of changes: - Included `update_operation` in `generator.yaml` file in order to make updates. - Implemented update tags support for VpcEndpoint 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 dfbbfd7 commit 99eedf9

File tree

8 files changed

+261
-9
lines changed

8 files changed

+261
-9
lines changed
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
ack_generate_info:
2-
build_date: "2022-09-14T17:04:56Z"
2+
build_date: "2022-09-14T19:52:51Z"
33
build_hash: 1da44f4be322eea156ed23a86e33c29da5cede9c
4-
go_version: go1.18.1
4+
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: 753e88b4fc3df1b908f63a998ffe37497ec57da1
10+
file_checksum: 54d118008c1b41e15473e323dbc940d79301b228
1111
original_file_name: generator.yaml
1212
last_modification:
1313
reason: API generation

apis/v1alpha1/generator.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,4 +494,9 @@ resources:
494494
sdk_create_post_build_request:
495495
template_path: hooks/vpc_endpoint/sdk_create_post_build_request.go.tpl
496496
sdk_delete_post_build_request:
497-
template_path: hooks/vpc_endpoint/sdk_delete_post_build_request.go.tpl
497+
template_path: hooks/vpc_endpoint/sdk_delete_post_build_request.go.tpl
498+
sdk_file_end:
499+
template_path: hooks/vpc_endpoint/sdk_file_end.go.tpl
500+
update_operation:
501+
custom_method_name: customUpdateVpcEndpoint
502+

generator.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,4 +494,9 @@ resources:
494494
sdk_create_post_build_request:
495495
template_path: hooks/vpc_endpoint/sdk_create_post_build_request.go.tpl
496496
sdk_delete_post_build_request:
497-
template_path: hooks/vpc_endpoint/sdk_delete_post_build_request.go.tpl
497+
template_path: hooks/vpc_endpoint/sdk_delete_post_build_request.go.tpl
498+
sdk_file_end:
499+
template_path: hooks/vpc_endpoint/sdk_file_end.go.tpl
500+
update_operation:
501+
custom_method_name: customUpdateVpcEndpoint
502+

pkg/resource/vpc_endpoint/hooks.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@
1414
package vpc_endpoint
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"
23+
1924
svcsdk "github.com/aws/aws-sdk-go/service/ec2"
2025
)
2126

@@ -30,6 +35,129 @@ func addIDToDeleteRequest(r *resource,
3035
return nil
3136
}
3237

38+
func (rm *resourceManager) customUpdateVpcEndpoint(
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.customUpdateVpcEndpoint")
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.VPCEndpointID}
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 vpcEndpoint 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 vpcEndpoint 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+
33161
// updateTagSpecificationsInCreateRequest adds
34162
// Tags defined in the Spec to CreateVpcEndpointInput.TagSpecification
35163
// and ensures the ResourceType is always set to 'vpc-endpoint'

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

test/e2e/resources/vpc_endpoint.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ metadata:
44
name: $VPC_ENDPOINT_NAME
55
spec:
66
serviceName: $SERVICE_NAME
7-
vpcID: $VPC_ID
7+
vpcID: $VPC_ID
8+
tags:
9+
- key: $TAG_KEY
10+
value: $TAG_VALUE

test/e2e/tests/test_vpc_endpoint.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@
3333

3434
CREATE_WAIT_AFTER_SECONDS = 10
3535
DELETE_WAIT_AFTER_SECONDS = 10
36+
MODIFY_WAIT_AFTER_SECONDS = 5
3637

3738
@pytest.fixture
38-
def simple_vpc_endpoint():
39+
def simple_vpc_endpoint(request):
3940
test_resource_values = REPLACEMENT_VALUES.copy()
4041
resource_name = random_suffix_name("vpc-endpoint-test", 24)
4142
test_vpc = get_bootstrap_resources().SharedTestVPC
@@ -45,6 +46,14 @@ def simple_vpc_endpoint():
4546
test_resource_values["SERVICE_NAME"] = ENDPOINT_SERVICE_NAME
4647
test_resource_values["VPC_ID"] = vpc_id
4748

49+
marker = request.node.get_closest_marker("resource_data")
50+
if marker is not None:
51+
data = marker.args[0]
52+
if 'tag_key' in data:
53+
test_resource_values["TAG_KEY"] = data["tag_key"]
54+
if 'tag_value' in data:
55+
test_resource_values["TAG_VALUE"] = data["tag_value"]
56+
4857
# Load VPC Endpoint CR
4958
resource_data = load_ec2_resource(
5059
"vpc_endpoint",
@@ -94,6 +103,72 @@ def test_create_delete(self, ec2_client, simple_vpc_endpoint):
94103

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

98173
def test_terminal_condition_malformed_vpc(self):
99174
test_resource_values = REPLACEMENT_VALUES.copy()

0 commit comments

Comments
 (0)