Skip to content

Commit b522378

Browse files
authored
support tag updates for DBSubnetGroup resources (#76)
Adds support for tagging and updating DBSubnetGroup resources. Issue aws-controllers-k8s/community#1276 Signed-off-by: Jay Pipes <[email protected]> By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 94c0048 commit b522378

File tree

12 files changed

+430
-39
lines changed

12 files changed

+430
-39
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-05-10T15:08:44Z"
2+
build_date: "2022-05-11T16:29:30Z"
33
build_hash: c6efa6ac643edb21219e0763541b2558718b5fe6
44
go_version: go1.18.1
55
version: v0.18.4-10-gc6efa6a
66
api_directory_checksum: e7bbd21f4f975f9cf1e1e804ebd450e8e310023d
77
api_version: v1alpha1
88
aws_sdk_go_version: v1.42.0
99
generator_config_info:
10-
file_checksum: ad1e93741dacbcf19188d95771c12957936a5e1d
10+
file_checksum: 977d8d553f526f7d5b76d8ea9d82e5798de16e39
1111
original_file_name: generator.yaml
1212
last_modification:
1313
reason: API generation

apis/v1alpha1/generator.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,26 @@ resources:
242242
input_fields:
243243
DBSubnetGroupName: Name
244244
DBSubnetGroupDescription: Description
245+
ModifyDBSubnetGroup:
246+
input_fields:
247+
DBSubnetGroupName: Name
248+
DBSubnetGroupDescription: Description
245249
DeleteDBSubnetGroup:
246250
input_fields:
247251
DBSubnetGroupName: Name
252+
exceptions:
253+
terminal_codes:
254+
- DBSubnetGroupDoesNotCoverEnoughAZs
255+
- InvalidSubnet
256+
- InvalidParameter
257+
- SubnetAlreadyInUse
258+
hooks:
259+
sdk_read_many_post_set_output:
260+
template_path: hooks/db_subnet_group/sdk_read_many_post_set_output.go.tpl
261+
sdk_update_pre_set_output:
262+
template_path: hooks/db_subnet_group/sdk_update_pre_set_output.go.tpl
263+
delta_pre_compare:
264+
template_path: hooks/db_subnet_group/delta_pre_compare.go.tpl
248265
fields:
249266
Name:
250267
is_primary_key: true

generator.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,26 @@ resources:
242242
input_fields:
243243
DBSubnetGroupName: Name
244244
DBSubnetGroupDescription: Description
245+
ModifyDBSubnetGroup:
246+
input_fields:
247+
DBSubnetGroupName: Name
248+
DBSubnetGroupDescription: Description
245249
DeleteDBSubnetGroup:
246250
input_fields:
247251
DBSubnetGroupName: Name
252+
exceptions:
253+
terminal_codes:
254+
- DBSubnetGroupDoesNotCoverEnoughAZs
255+
- InvalidSubnet
256+
- InvalidParameter
257+
- SubnetAlreadyInUse
258+
hooks:
259+
sdk_read_many_post_set_output:
260+
template_path: hooks/db_subnet_group/sdk_read_many_post_set_output.go.tpl
261+
sdk_update_pre_set_output:
262+
template_path: hooks/db_subnet_group/sdk_update_pre_set_output.go.tpl
263+
delta_pre_compare:
264+
template_path: hooks/db_subnet_group/delta_pre_compare.go.tpl
248265
fields:
249266
Name:
250267
is_primary_key: true

pkg/resource/db_subnet_group/delta.go

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

pkg/resource/db_subnet_group/hooks.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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 db_subnet_group
15+
16+
import (
17+
"context"
18+
19+
ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare"
20+
ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log"
21+
22+
svcapitypes "github.com/aws-controllers-k8s/rds-controller/apis/v1alpha1"
23+
svcsdk "github.com/aws/aws-sdk-go/service/rds"
24+
)
25+
26+
// syncTags keeps the resource's tags in sync
27+
//
28+
// NOTE(jaypipes): RDS' Tagging APIs differ from other AWS APIs in the
29+
// following ways:
30+
//
31+
// 1. The names of the tagging API operations are different. Other APIs use the
32+
// Tagris `ListTagsForResource`, `TagResource` and `UntagResource` API
33+
// calls. RDS uses `ListTagsForResource`, `AddTagsToResource` and
34+
// `RemoveTagsFromResource`.
35+
//
36+
// 2. Even though the name of the `ListTagsForResource` API call is the same,
37+
// the structure of the input and the output are different from other APIs.
38+
// For the input, instead of a `ResourceArn` field, RDS names the field
39+
// `ResourceName`, but actually expects an ARN, not the subnet group
40+
// name. This is the same for the `AddTagsToResource` and
41+
// `RemoveTagsFromResource` input shapes. For the output shape, the field is
42+
// called `TagList` instead of `Tags` but is otherwise the same struct with
43+
// a `Key` and `Value` member field.
44+
func (rm *resourceManager) syncTags(
45+
ctx context.Context,
46+
desired *resource,
47+
latest *resource,
48+
) (err error) {
49+
rlog := ackrtlog.FromContext(ctx)
50+
exit := rlog.Trace("rm.syncTags")
51+
defer func() { exit(err) }()
52+
53+
arn := (*string)(latest.ko.Status.ACKResourceMetadata.ARN)
54+
55+
toAdd, toDelete := computeTagsDelta(
56+
desired.ko.Spec.Tags, latest.ko.Spec.Tags,
57+
)
58+
59+
if len(toDelete) > 0 {
60+
rlog.Debug("removing tags from subnet group", "tags", toDelete)
61+
_, err = rm.sdkapi.RemoveTagsFromResourceWithContext(
62+
ctx,
63+
&svcsdk.RemoveTagsFromResourceInput{
64+
ResourceName: arn,
65+
TagKeys: toDelete,
66+
},
67+
)
68+
rm.metrics.RecordAPICall("UPDATE", "RemoveTagsFromResource", err)
69+
if err != nil {
70+
return err
71+
}
72+
}
73+
74+
// NOTE(jaypipes): According to the RDS API documentation, adding a tag
75+
// with a new value overwrites any existing tag with the same key. So, we
76+
// don't need to do anything to "update" a Tag. Simply including it in the
77+
// AddTagsToResource call is enough.
78+
if len(toAdd) > 0 {
79+
rlog.Debug("adding tags to subnet group", "tags", toAdd)
80+
_, err = rm.sdkapi.AddTagsToResourceWithContext(
81+
ctx,
82+
&svcsdk.AddTagsToResourceInput{
83+
ResourceName: arn,
84+
Tags: sdkTagsFromResourceTags(toAdd),
85+
},
86+
)
87+
rm.metrics.RecordAPICall("UPDATE", "AddTagsToResource", err)
88+
if err != nil {
89+
return err
90+
}
91+
}
92+
return nil
93+
}
94+
95+
// getTags retrieves the resource's associated tags
96+
func (rm *resourceManager) getTags(
97+
ctx context.Context,
98+
resourceARN string,
99+
) ([]*svcapitypes.Tag, error) {
100+
resp, err := rm.sdkapi.ListTagsForResourceWithContext(
101+
ctx,
102+
&svcsdk.ListTagsForResourceInput{
103+
ResourceName: &resourceARN,
104+
},
105+
)
106+
rm.metrics.RecordAPICall("GET", "ListTagsForResource", err)
107+
if err != nil {
108+
return nil, err
109+
}
110+
tags := make([]*svcapitypes.Tag, 0, len(resp.TagList))
111+
for _, tag := range resp.TagList {
112+
tags = append(tags, &svcapitypes.Tag{
113+
Key: tag.Key,
114+
Value: tag.Value,
115+
})
116+
}
117+
return tags, nil
118+
}
119+
120+
// compareTags adds a difference to the delta if the supplied resources have
121+
// different tag collections
122+
func compareTags(
123+
delta *ackcompare.Delta,
124+
a *resource,
125+
b *resource,
126+
) {
127+
if len(a.ko.Spec.Tags) != len(b.ko.Spec.Tags) {
128+
delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags)
129+
} else if len(a.ko.Spec.Tags) > 0 {
130+
if !equalTags(a.ko.Spec.Tags, b.ko.Spec.Tags) {
131+
delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags)
132+
}
133+
}
134+
}
135+
136+
// equalTags returns true if two Tag arrays are equal regardless of the order
137+
// of their elements.
138+
func equalTags(
139+
a []*svcapitypes.Tag,
140+
b []*svcapitypes.Tag,
141+
) bool {
142+
added, removed := computeTagsDelta(a, b)
143+
return len(added) == 0 && len(removed) == 0
144+
}
145+
146+
// computeTagsDelta compares two Tag arrays and returns the tags to add and the
147+
// tag keys to delete
148+
func computeTagsDelta(
149+
desired []*svcapitypes.Tag,
150+
latest []*svcapitypes.Tag,
151+
) ([]*svcapitypes.Tag, []*string) {
152+
toDelete := []*string{}
153+
toAdd := []*svcapitypes.Tag{}
154+
155+
desiredTags := map[string]string{}
156+
for _, tag := range desired {
157+
desiredTags[*tag.Key] = *tag.Value
158+
}
159+
160+
for _, tag := range desired {
161+
toAdd = append(toAdd, tag)
162+
}
163+
for _, tag := range latest {
164+
_, ok := desiredTags[*tag.Key]
165+
if !ok {
166+
toDelete = append(toDelete, tag.Key)
167+
}
168+
}
169+
return toAdd, toDelete
170+
}
171+
172+
// sdkTagsFromResourceTags transforms a *svcapitypes.Tag array to a *svcsdk.Tag
173+
// array.
174+
func sdkTagsFromResourceTags(
175+
rTags []*svcapitypes.Tag,
176+
) []*svcsdk.Tag {
177+
tags := make([]*svcsdk.Tag, len(rTags))
178+
for i := range rTags {
179+
tags[i] = &svcsdk.Tag{
180+
Key: rTags[i].Key,
181+
Value: rTags[i].Value,
182+
}
183+
}
184+
return tags
185+
}
186+
187+
func equalStrings(a, b *string) bool {
188+
if a == nil {
189+
return b == nil || *b == ""
190+
}
191+
return (*a == "" && b == nil) || *a == *b
192+
}

pkg/resource/db_subnet_group/sdk.go

Lines changed: 46 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
compareTags(delta, a, b)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
if ko.Status.ACKResourceMetadata != nil && ko.Status.ACKResourceMetadata.ARN != nil {
2+
resourceARN := (*string)(ko.Status.ACKResourceMetadata.ARN)
3+
tags, err := rm.getTags(ctx, *resourceARN)
4+
if err != nil {
5+
return nil, err
6+
}
7+
ko.Spec.Tags = tags
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
if delta.DifferentAt("Spec.Tags") {
2+
if err = rm.syncTags(ctx, desired, latest); err != nil {
3+
return nil, err
4+
}
5+
}

0 commit comments

Comments
 (0)