Skip to content

Commit d13b586

Browse files
Compare tags using common tag type (#399)
Implements aws-controllers-k8s/community#1658 Description of changes: Instead of comparing tag fields using the standard `reflect.DeepEquals`, this PR updates the code-generator to convert the tags to the common `acktags.Tag` type and then compare those as maps. This ensures that changes in ordering do not result in false positive deltas. Controllers that rely on custom tag delta functions also typically use a function to determine which tags are being added and which are being removed. Using the same trick as this PR, a method can be added to the common runtime to deduce those - resulting in far less duplicated and verbose custom hook code. Those changes are not included in this PR, though. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 8dc09b7 commit d13b586

File tree

6 files changed

+93
-10
lines changed

6 files changed

+93
-10
lines changed

pkg/generate/code/compare.go

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ func CompareResource(
9999

100100
resConfig := cfg.GetResourceConfig(r.Names.Camel)
101101

102+
tagField, err := r.GetTagField()
103+
if err != nil {
104+
panic(err)
105+
}
106+
102107
// We need a deterministic order to traverse our top-level fields...
103108
specFieldNames := []string{}
104109
for fieldName := range r.SpecFields {
@@ -147,10 +152,18 @@ func CompareResource(
147152
out += fmt.Sprintf("%s}\n", indent)
148153
continue
149154
}
155+
156+
// Use a special comparison model for tags, since they need to be
157+
// converted into the common ACK tag type before doing a map delta
158+
if tagField != nil && specField == tagField {
159+
out += compareTags(deltaVarName, firstResAdaptedVarName, secondResAdaptedVarName, fieldPath, indentLevel)
160+
continue
161+
}
162+
150163
memberShapeRef := specField.ShapeRef
151164
memberShape := memberShapeRef.Shape
152165

153-
// if ackcompare.HasNilDifference(a.ko.Spec.Name, b.ko.Spec.Name == nil) {
166+
// if ackcompare.HasNilDifference(a.ko.Spec.Name, b.ko.Spec.Name) {
154167
// delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name)
155168
// }
156169
nilCode := compareNil(
@@ -520,6 +533,46 @@ func compareSlice(
520533
return out
521534
}
522535

536+
// compareTags outputs Go code that compares two slices of tags from two
537+
// resource fields by first converting them to the common ACK tag type and then
538+
// using a map comparison. If there is a difference, adds the difference to a
539+
// variable representing an `ackcompare.Delta`.
540+
//
541+
// Output code will look something like this:
542+
//
543+
// if !ackcompare.MapStringStringEqual(ToACKTags(a.ko.Spec.Tags), ToACKTags(b.ko.Spec.Tags)) {
544+
// delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags)
545+
// }
546+
func compareTags(
547+
// String representing the name of the variable that is of type
548+
// `*ackcompare.Delta`. We will generate Go code that calls the `Add()`
549+
// method of this variable when differences between fields are detected.
550+
deltaVarName string,
551+
// String representing the name of the variable that represents the first
552+
// CR under comparison. This will typically be something like
553+
// "a.ko.Spec.Name". See `templates/pkg/resource/delta.go.tpl`.
554+
firstResVarName string,
555+
// String representing the name of the variable that represents the second
556+
// CR under comparison. This will typically be something like
557+
// "b.ko.Spec.Name". See `templates/pkg/resource/delta.go.tpl`.
558+
secondResVarName string,
559+
// String indicating the current field path being evaluated, e.g.
560+
// "Author.Name". This does not include the top-level Spec or Status
561+
// struct.
562+
fieldPath string,
563+
// Number of levels of indentation to use
564+
indentLevel int,
565+
) string {
566+
out := ""
567+
indent := strings.Repeat("\t", indentLevel)
568+
569+
out += fmt.Sprintf("%sif !ackcompare.MapStringStringEqual(ToACKTags(%s), ToACKTags(%s)) {\n", indent, firstResVarName, secondResVarName)
570+
out += fmt.Sprintf("%s\t%s.Add(\"%s\", %s, %s)\n", indent, deltaVarName, fieldPath, firstResVarName, secondResVarName)
571+
out += fmt.Sprintf("%s}\n", indent)
572+
573+
return out
574+
}
575+
523576
// CompareStruct outputs Go code that compares two struct values from two
524577
// resource fields and, if there is a difference, adds the difference to a
525578
// variable representing an `ackcompare.Delta`.
@@ -551,6 +604,11 @@ func CompareStruct(
551604
) string {
552605
out := ""
553606

607+
tagField, err := r.GetTagField()
608+
if err != nil {
609+
panic(err)
610+
}
611+
554612
fieldConfigs := cfg.GetFieldConfigs(r.Names.Original)
555613

556614
for _, memberName := range shape.MemberNames() {
@@ -572,7 +630,9 @@ func CompareStruct(
572630
// memberFieldPath contains the field path along with the prefix cfg.PrefixConfig.SpecField + "." hence we
573631
// would need to substring to exclude cfg.PrefixConfig.SpecField + "." to get correct field config.
574632
specFieldLen := len(strings.TrimPrefix(cfg.PrefixConfig.SpecField, "."))
575-
fieldConfig := fieldConfigs[memberFieldPath[specFieldLen+1:len(memberFieldPath)]]
633+
trimmedFieldPath := memberFieldPath[specFieldLen+1:]
634+
635+
fieldConfig := fieldConfigs[trimmedFieldPath]
576636
if fieldConfig != nil {
577637
compareConfig = fieldConfig.Compare
578638
}
@@ -583,6 +643,13 @@ func CompareStruct(
583643

584644
memberShape := memberShapeRef.Shape
585645

646+
// Use a special comparison model for tags, since they need to be
647+
// converted into the common ACK tag type before doing a map delta
648+
if tagField != nil && tagField.Path == trimmedFieldPath {
649+
out += compareTags(deltaVarName, firstResAdaptedVarName, secondResAdaptedVarName, fieldPath, indentLevel)
650+
continue
651+
}
652+
586653
// if ackcompare.HasNilDifference(a.ko.Spec.Name, b.ko.Spec.Name == nil) {
587654
// delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name)
588655
// }
@@ -604,6 +671,7 @@ func CompareStruct(
604671
)
605672
indentLevel++
606673
}
674+
607675
switch memberShape.Type {
608676
case "structure":
609677
// Recurse through all the struct's fields and subfields, building

pkg/generate/code/compare_test.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ func TestCompareResource_S3_Bucket(t *testing.T) {
120120
delta.Add("Spec.ObjectLockEnabledForBucket", a.ko.Spec.ObjectLockEnabledForBucket, b.ko.Spec.ObjectLockEnabledForBucket)
121121
}
122122
}
123+
if ackcompare.HasNilDifference(a.ko.Spec.Tagging, b.ko.Spec.Tagging) {
124+
delta.Add("Spec.Tagging", a.ko.Spec.Tagging, b.ko.Spec.Tagging)
125+
} else if a.ko.Spec.Tagging != nil && b.ko.Spec.Tagging != nil {
126+
if !ackcompare.MapStringStringEqual(ToACKTags(a.ko.Spec.Tagging.TagSet), ToACKTags(b.ko.Spec.Tagging.TagSet)) {
127+
delta.Add("Spec.Tagging", a.ko.Spec.Tagging.TagSet, b.ko.Spec.Tagging.TagSet)
128+
}
129+
}
123130
`
124131
assert.Equal(
125132
expected,
@@ -333,12 +340,8 @@ func TestCompareResource_Lambda_Function(t *testing.T) {
333340
delta.Add("Spec.Runtime", a.ko.Spec.Runtime, b.ko.Spec.Runtime)
334341
}
335342
}
336-
if ackcompare.HasNilDifference(a.ko.Spec.Tags, b.ko.Spec.Tags) {
343+
if !ackcompare.MapStringStringEqual(ToACKTags(a.ko.Spec.Tags), ToACKTags(b.ko.Spec.Tags)) {
337344
delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags)
338-
} else if a.ko.Spec.Tags != nil && b.ko.Spec.Tags != nil {
339-
if !ackcompare.MapStringStringPEqual(a.ko.Spec.Tags, b.ko.Spec.Tags) {
340-
delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags)
341-
}
342345
}
343346
if ackcompare.HasNilDifference(a.ko.Spec.Timeout, b.ko.Spec.Timeout) {
344347
delta.Add("Spec.Timeout", a.ko.Spec.Timeout, b.ko.Spec.Timeout)
@@ -490,7 +493,7 @@ func TestCompareResource_IAM_OIDC_URL(t *testing.T) {
490493
if !ackcompare.SliceStringPEqual(a.ko.Spec.ClientIDList, b.ko.Spec.ClientIDList) {
491494
delta.Add("Spec.ClientIDList", a.ko.Spec.ClientIDList, b.ko.Spec.ClientIDList)
492495
}
493-
if !reflect.DeepEqual(a.ko.Spec.Tags, b.ko.Spec.Tags) {
496+
if !ackcompare.MapStringStringEqual(ToACKTags(a.ko.Spec.Tags), ToACKTags(b.ko.Spec.Tags)) {
494497
delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags)
495498
}
496499
if !ackcompare.SliceStringPEqual(a.ko.Spec.ThumbprintList, b.ko.Spec.ThumbprintList) {
@@ -539,7 +542,7 @@ func TestCompareResource_MemoryDB_User(t *testing.T) {
539542
delta.Add("Spec.Name", a.ko.Spec.Name, b.ko.Spec.Name)
540543
}
541544
}
542-
if !reflect.DeepEqual(a.ko.Spec.Tags, b.ko.Spec.Tags) {
545+
if !ackcompare.MapStringStringEqual(ToACKTags(a.ko.Spec.Tags), ToACKTags(b.ko.Spec.Tags)) {
543546
delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags)
544547
}
545548
`

pkg/model/model_s3_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ func TestS3_Bucket(t *testing.T) {
8282
// ListBuckets API call...
8383
"Name",
8484
"ObjectLockEnabledForBucket",
85+
"Tagging",
8586
}
8687
assert.Equal(expSpecFieldCamel, attrCamelNames(specFields))
8788

pkg/testdata/models/apis/lambda/0000-00-00/generator.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ resources:
2525
in:
2626
- 1
2727
- 2
28+
CodeSigningConfig:
29+
tags:
30+
ignore: true

pkg/testdata/models/apis/s3/0000-00-00/generator.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ resources:
1818
list_operation:
1919
match_fields:
2020
- Name
21+
tags:
22+
path: Tagging.TagSet
2123
fields:
2224
ACL:
2325
# This is to test the ackcompare field ignore functionality. This
@@ -27,4 +29,8 @@ resources:
2729
Logging:
2830
from:
2931
operation: PutBucketLogging
30-
path: BucketLoggingStatus
32+
path: BucketLoggingStatus
33+
Tagging:
34+
from:
35+
operation: PutBucketTagging
36+
path: Tagging

templates/pkg/resource/delta.go.tpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import (
77
"reflect"
88

99
ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare"
10+
acktags "github.com/aws-controllers-k8s/runtime/pkg/tags"
1011
)
1112

1213
// Hack to avoid import errors during build...
1314
var (
1415
_ = &bytes.Buffer{}
1516
_ = &reflect.Method{}
17+
_ = &acktags.Tags{}
1618
)
1719

1820
// newResourceDelta returns a new `ackcompare.Delta` used to compare two

0 commit comments

Comments
 (0)