Skip to content

Commit 0ffda63

Browse files
authored
Support tag updates for Certificate resource (#32)
Adding tag update support for `Certificate` resource + e2e tests for the new feature. > Most of this code is copy-pasted and refined to fit the ACM API, the end goal is generate all this code after detecting most of the patterns if not all of them. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 6777944 commit 0ffda63

File tree

11 files changed

+323
-28
lines changed

11 files changed

+323
-28
lines changed
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
ack_generate_info:
2-
build_date: "2024-02-14T04:06:57Z"
3-
build_hash: 947081ffebdeefcf2c61c4ca6d7e68810bdf9d08
2+
build_date: "2024-02-19T08:00:53Z"
3+
build_hash: e8313de46f391b8c8044963a1becf781ffd7a326
44
go_version: go1.22.0
5-
version: v0.30.0
6-
api_directory_checksum: eabe0fe64d57edf571ba0eb0217fc376f1185cc0
5+
version: v0.30.0-6-ge8313de
6+
api_directory_checksum: e337526dd1438ddb861e06bd92b5f640ba9ed537
77
api_version: v1alpha1
88
aws_sdk_go_version: v1.49.0
99
generator_config_info:
10-
file_checksum: 229489e50bc34730f31e2e0578bec6f9ea7d7215
10+
file_checksum: 910047b7946c5968bbc58b6334099deb005e95cc
1111
original_file_name: generator.yaml
1212
last_modification:
1313
reason: API generation

apis/v1alpha1/generator.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ operations:
1717
resources:
1818
Certificate:
1919
hooks:
20+
sdk_update_pre_build_request:
21+
template_path: hooks/certificate/sdk_update_pre_build_request.go.tpl
2022
sdk_create_pre_build_request:
2123
template_path: hooks/certificate/sdk_create_pre_build_request.go.tpl
2224
sdk_create_post_build_request:

generator.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ operations:
1717
resources:
1818
Certificate:
1919
hooks:
20+
sdk_update_pre_build_request:
21+
template_path: hooks/certificate/sdk_update_pre_build_request.go.tpl
2022
sdk_create_pre_build_request:
2123
template_path: hooks/certificate/sdk_create_pre_build_request.go.tpl
2224
sdk_create_post_build_request:

pkg/resource/certificate/hooks.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,11 @@ package certificate
1616
import (
1717
"errors"
1818
"fmt"
19-
"time"
19+
20+
"github.com/aws-controllers-k8s/acm-controller/pkg/tags"
2021
)
2122

2223
const (
23-
// See note on
24-
// https://docs.aws.amazon.com/acm/latest/APIReference/API_RequestCertificate.html
25-
// about DescribeCertificate not being ready to call for several seconds
26-
// after a successful RequestCertificate API call...
27-
waitSecondsAfterCreate = 5 * time.Second
28-
2924
// DNS validation only works for up to 5 chained CNAME records
3025
limitDomainValidationOptionsPublic = 5
3126
)
@@ -63,6 +58,7 @@ func validatePublicValidationOptions(
6358
return nil
6459
}
6560

66-
func waitAfterSuccessfulCreate() {
67-
time.Sleep(waitSecondsAfterCreate)
68-
}
61+
var (
62+
syncTags = tags.SyncTags
63+
listTags = tags.ListTags
64+
)

pkg/resource/certificate/sdk.go

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/tags/sync.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package tags
15+
16+
import (
17+
"context"
18+
19+
"github.com/aws-controllers-k8s/acm-controller/apis/v1alpha1"
20+
ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log"
21+
22+
"github.com/aws/aws-sdk-go/aws/request"
23+
svcsdk "github.com/aws/aws-sdk-go/service/acm"
24+
)
25+
26+
type metricsRecorder interface {
27+
RecordAPICall(opType string, opID string, err error)
28+
}
29+
30+
type tagsClient interface {
31+
AddTagsToCertificateWithContext(context.Context, *svcsdk.AddTagsToCertificateInput, ...request.Option) (*svcsdk.AddTagsToCertificateOutput, error)
32+
ListTagsForCertificateWithContext(context.Context, *svcsdk.ListTagsForCertificateInput, ...request.Option) (*svcsdk.ListTagsForCertificateOutput, error)
33+
RemoveTagsFromCertificateWithContext(context.Context, *svcsdk.RemoveTagsFromCertificateInput, ...request.Option) (*svcsdk.RemoveTagsFromCertificateOutput, error)
34+
}
35+
36+
// syncTags examines the Tags in the supplied Resource and calls the
37+
// TagResource and UntagResource APIs to ensure that the set of
38+
// associated Tags stays in sync with the Resource.Spec.Tags
39+
func SyncTags(
40+
ctx context.Context,
41+
client tagsClient,
42+
mr metricsRecorder,
43+
resourceID string,
44+
aTags []*v1alpha1.Tag,
45+
bTags []*v1alpha1.Tag,
46+
) (err error) {
47+
rlog := ackrtlog.FromContext(ctx)
48+
exit := rlog.Trace("rm.syncTags")
49+
defer func() { exit(err) }()
50+
51+
desiredTags := map[string]*string{}
52+
for _, t := range aTags {
53+
desiredTags[*t.Key] = t.Value
54+
}
55+
existingTags := map[string]*string{}
56+
for _, t := range bTags {
57+
existingTags[*t.Key] = t.Value
58+
}
59+
60+
toAdd := map[string]*string{}
61+
toDelete := map[string]*string{}
62+
63+
for k, v := range desiredTags {
64+
if ev, found := existingTags[k]; !found || *ev != *v {
65+
toAdd[k] = v
66+
}
67+
}
68+
69+
for k, v := range existingTags {
70+
if _, found := desiredTags[k]; !found {
71+
toDelete[k] = v
72+
}
73+
}
74+
75+
if len(toDelete) > 0 {
76+
for k, v := range toDelete {
77+
rlog.Debug("removing tag from resource", "key", k, "value", *v)
78+
}
79+
if err = removeTags(
80+
ctx,
81+
client,
82+
mr,
83+
resourceID,
84+
toDelete,
85+
); err != nil {
86+
return err
87+
}
88+
}
89+
if len(toAdd) > 0 {
90+
for k, v := range toAdd {
91+
rlog.Debug("adding tag to resource", "key", k, "value", *v)
92+
}
93+
if err = addTags(
94+
ctx,
95+
client,
96+
mr,
97+
resourceID,
98+
toAdd,
99+
); err != nil {
100+
return err
101+
}
102+
}
103+
104+
return nil
105+
}
106+
107+
// addTags adds the supplied Tags to the supplied resource
108+
func addTags(
109+
ctx context.Context,
110+
client tagsClient,
111+
mr metricsRecorder,
112+
resourceARN string,
113+
tags map[string]*string,
114+
) (err error) {
115+
rlog := ackrtlog.FromContext(ctx)
116+
exit := rlog.Trace("rm.addTag")
117+
defer func() { exit(err) }()
118+
119+
sdkTags := []*svcsdk.Tag{}
120+
for k, v := range tags {
121+
k := k
122+
sdkTags = append(sdkTags, &svcsdk.Tag{
123+
Key: &k,
124+
Value: v,
125+
})
126+
}
127+
128+
input := &svcsdk.AddTagsToCertificateInput{
129+
CertificateArn: &resourceARN,
130+
Tags: sdkTags,
131+
}
132+
133+
_, err = client.AddTagsToCertificateWithContext(ctx, input)
134+
mr.RecordAPICall("UPDATE", "AddTagsToCertificate", err)
135+
return err
136+
}
137+
138+
// removeTags removes the supplied Tags from the supplied resource
139+
func removeTags(
140+
ctx context.Context,
141+
client tagsClient,
142+
mr metricsRecorder,
143+
resourceARN string,
144+
tags map[string]*string,
145+
) (err error) {
146+
rlog := ackrtlog.FromContext(ctx)
147+
exit := rlog.Trace("rm.removeTag")
148+
defer func() { exit(err) }()
149+
150+
sdkTags := []*svcsdk.Tag{}
151+
for k, v := range tags {
152+
k := k
153+
sdkTags = append(sdkTags, &svcsdk.Tag{
154+
Key: &k,
155+
Value: v,
156+
})
157+
}
158+
159+
input := &svcsdk.RemoveTagsFromCertificateInput{
160+
CertificateArn: &resourceARN,
161+
Tags: sdkTags,
162+
}
163+
_, err = client.RemoveTagsFromCertificateWithContext(ctx, input)
164+
mr.RecordAPICall("UPDATE", "RemoveTagsFromCertificate", err)
165+
return err
166+
}
167+
168+
// getResourceTagsPagesWithContext queries the list of tags of a given resource.
169+
func ListTags(
170+
ctx context.Context,
171+
client tagsClient,
172+
mr metricsRecorder,
173+
resourceARN string,
174+
) ([]*v1alpha1.Tag, error) {
175+
var err error
176+
rlog := ackrtlog.FromContext(ctx)
177+
exit := rlog.Trace("rm.listTags")
178+
defer exit(err)
179+
180+
var listTagsOfResourceOutput *svcsdk.ListTagsForCertificateOutput
181+
listTagsOfResourceOutput, err = client.ListTagsForCertificateWithContext(
182+
ctx,
183+
&svcsdk.ListTagsForCertificateInput{
184+
CertificateArn: &resourceARN,
185+
},
186+
)
187+
mr.RecordAPICall("GET", "ListTagsForCertificate", err)
188+
if err != nil {
189+
return nil, err
190+
}
191+
return resourceTagsFromSDKTags(listTagsOfResourceOutput.Tags), nil
192+
}
193+
194+
// resourceTagsFromSDKTags transforms a *svcsdk.Tag array to a *v1alpha1.Tag array.
195+
func resourceTagsFromSDKTags(svcTags []*svcsdk.Tag) []*v1alpha1.Tag {
196+
tags := make([]*v1alpha1.Tag, len(svcTags))
197+
for i := range svcTags {
198+
tags[i] = &v1alpha1.Tag{
199+
Key: svcTags[i].Key,
200+
Value: svcTags[i].Value,
201+
}
202+
}
203+
return tags
204+
}

templates/hooks/certificate/sdk_read_one_pre_set_output.go.tpl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,10 @@
3535
} else {
3636
ko.Status.DomainValidations = nil
3737
}
38+
ko.Spec.Tags, err = listTags(
39+
ctx, rm.sdkapi, rm.metrics,
40+
string(*r.ko.Status.ACKResourceMetadata.ARN),
41+
)
42+
if err != nil {
43+
return nil, err
44+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
if delta.DifferentAt("Spec.Tags") {
2+
err := syncTags(
3+
ctx, rm.sdkapi, rm.metrics,
4+
string(*desired.ko.Status.ACKResourceMetadata.ARN),
5+
desired.ko.Spec.Tags, latest.ko.Spec.Tags,
6+
)
7+
if err != nil {
8+
return nil, err
9+
}
10+
}
11+
// If nothing else has changed, we shouldn't send an update.
12+
if !delta.DifferentExcept("Spec.Tags") {
13+
return desired, nil
14+
}

test/e2e/certificate.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,17 +118,17 @@ def get(certificate_arn):
118118
return None
119119

120120

121-
def get_tags(db_instance_arn):
121+
def get_tags(certificate_arn):
122122
"""Returns a dict containing the Certificate's tag records from the ACM
123123
API.
124124
125125
If no such Certificate exists, returns None.
126126
"""
127-
c = boto3.client('rds')
127+
c = boto3.client('acm')
128128
try:
129-
resp = c.list_tags_for_resource(
130-
ResourceName=db_instance_arn,
129+
resp = c.list_tags_for_certificate(
130+
CertificateArn=certificate_arn,
131131
)
132-
return resp['TagList']
133-
except c.exceptions.DBCertificateNotFoundFault:
132+
return resp['Tags']
133+
except c.exceptions.ResourceNotFoundException:
134134
return None

test/e2e/resources/certificate_public.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ spec:
77
# NOTE(jaypipes): Having an empty certificateAuthorityARN field indicates
88
# that this is a public certificate request...
99
tags:
10-
- key: environment
11-
value: dev
10+
- key: environment
11+
value: dev

0 commit comments

Comments
 (0)