@@ -25,75 +25,56 @@ import (
2525 "github.com/aws-controllers-k8s/code-generator/pkg/model"
2626)
2727
28- // CompareResource returns the Go code that traverses a set of two Resources,
29- // adding differences between the two Resources to an `ackcompare.Delta`
30- //
31- // By default, we produce Go code that only looks at the fields in a resource's
32- // Spec, since those are the fields that represent the desired state of a
33- // resource. When we make a ReadOne/ReadMany/GetAttributes call to a backend
34- // AWS API, we construct a Resource and set the Spec fields to values contained
35- // in the ReadOne/ReadMany/GetAttributes Output shape. This Resource,
36- // constructed from the Read operation, is compared to the Resource we got from
37- // the Kubernetes API server's event bus. The code that is returned from this
38- // function is the code that compares those two Resources.
39- //
40- // The Go code we return depends on the Go type of the various fields for the
41- // resource being compared.
42- //
43- // For *scalar* Go types, the output Go code looks like this:
44- //
45- // if ackcompare.HasNilDifference(a.ko.Spec.GrantFullControl, b.ko.Spec.GrantFullControl) {
46- // delta.Add("Spec.GrantFullControl", a.ko.Spec.GrantFullControl, b.ko.Spec.GrantFullControl)
47- // } else if a.ko.Spec.GrantFullControl != nil && b.ko.Spec.GrantFullControl != nil {
48- //
49- // if *a.ko.Spec.GrantFullControl != *b.ko.Spec.GrantFullControl {
50- // delta.Add("Spec.GrantFullControl", a.ko.Spec.GrantFullControl, b.ko.Spec.GrantFullControl)
51- // }
52- // }
53- //
54- // For *struct* Go types, the output Go code looks like this (note that it is a
55- // simple recursive-descent output of all the struct's fields...):
56- //
57- // if ackcompare.HasNilDifference(a.ko.Spec.CreateBucketConfiguration, b.ko.Spec.CreateBucketConfiguration) {
58- // delta.Add("Spec.CreateBucketConfiguration", a.ko.Spec.CreateBucketConfiguration, b.ko.Spec.CreateBucketConfiguration)
59- // } else if a.ko.Spec.CreateBucketConfiguration != nil && b.ko.Spec.CreateBucketConfiguration != nil {
60- //
61- // if ackcompare.HasNilDifference(a.ko.Spec.CreateBucketConfiguration.LocationConstraint, b.ko.Spec.CreateBucketConfiguration.LocationConstraint) {
62- // delta.Add("Spec.CreateBucketConfiguration.LocationConstraint", a.ko.Spec.CreateBucketConfiguration.LocationConstraint, b.ko.Spec.CreateBucketConfiguration.LocationConstraint)
63- // } else if a.ko.Spec.CreateBucketConfiguration.LocationConstraint != nil && b.ko.Spec.CreateBucketConfiguration.LocationConstraint != nil {
64- // if *a.ko.Spec.CreateBucketConfiguration.LocationConstraint != *b.ko.Spec.CreateBucketConfiguration.LocationConstraint {
65- // delta.Add("Spec.CreateBucketConfiguration.LocationConstraint", a.ko.Spec.CreateBucketConfiguration.LocationConstraint, b.ko.Spec.CreateBucketConfiguration.LocationConstraint)
66- // }
67- // }
68- // }
69- //
70- // For *slice of strings* Go types, the output Go code looks like this:
71- //
72- // if ackcompare.HasNilDifference(a.ko.Spec.AllowedPublishers, b.ko.Spec.AllowedPublishers) {
73- // delta.Add("Spec.AllowedPublishers", a.ko.Spec.AllowedPublishers, b.ko.Spec.AllowedPublishers)
74- // } else if a.ko.Spec.AllowedPublishers != nil && b.ko.Spec.AllowedPublishers != nil {
75- //
76- // if !ackcompare.SliceStringPEqual(a.ko.Spec.AllowedPublishers.SigningProfileVersionARNs, b.ko.Spec.AllowedPublishers.SigningProfileVersionARNs) {
77- // delta.Add("Spec.AllowedPublishers.SigningProfileVersionARNs", a.ko.Spec.AllowedPublishers.SigningProfileVersionARNs, b.ko.Spec.AllowedPublishers.SigningProfileVersionARNs)
78- // }
79- // }
80- func CompareResource (
28+ // HasPreDeleteSync returns true if the CRD has any pre-delete sync
29+ // configuration — either pre_delete_sync.compare_all is true, or at least
30+ // one field has compare.pre_delete_include: true.
31+ func HasPreDeleteSync (
32+ cfg * ackgenconfig.Config ,
33+ r * model.CRD ,
34+ ) bool {
35+ resConfig := cfg .GetResourceConfig (r .Names .Camel )
36+ if resConfig == nil {
37+ return false
38+ }
39+ if resConfig .PreDeleteSync != nil && resConfig .PreDeleteSync .CompareAll {
40+ return true
41+ }
42+ for fieldName := range r .SpecFields {
43+ fieldConfig := resConfig .GetFieldConfig (fieldName )
44+ if fieldConfig != nil && fieldConfig .Compare != nil && fieldConfig .Compare .PreDeleteInclude {
45+ return true
46+ }
47+ }
48+ return false
49+ }
50+
51+ // sortedSpecFieldNames returns the spec field names for a CRD in
52+ // deterministic sorted order.
53+ func sortedSpecFieldNames (r * model.CRD ) []string {
54+ fieldNames := make ([]string , 0 , len (r .SpecFields ))
55+ for fieldName := range r .SpecFields {
56+ fieldNames = append (fieldNames , fieldName )
57+ }
58+ sort .Strings (fieldNames )
59+ return fieldNames
60+ }
61+
62+ // fieldFilter is a function that determines whether a spec field should be
63+ // included in the comparison output. It receives the field's compare config
64+ // (which may be nil) and returns true if the field should be included.
65+ type fieldFilter func (compareConfig * ackgenconfig.CompareFieldConfig ) bool
66+
67+ // compareResourceFields is the shared implementation for generating Go code
68+ // that compares spec fields between two resources. The includeField callback
69+ // controls which fields are included in the output.
70+ func compareResourceFields (
8171 cfg * ackgenconfig.Config ,
8272 r * model.CRD ,
83- // String representing the name of the variable that is of type
84- // `*ackcompare.Delta`. We will generate Go code that calls the `Add()`
85- // method of this variable when differences between fields are detected.
8673 deltaVarName string ,
87- // String representing the name of the variable that represents the first
88- // CR under comparison. This will typically be something like "a.ko". See
89- // `templates/pkg/resource/delta.go.tpl`.
9074 firstResVarName string ,
91- // String representing the name of the variable that represents the second
92- // CR under comparison. This will typically be something like "b.ko". See
93- // `templates/pkg/resource/delta.go.tpl`.
9475 secondResVarName string ,
95- // Number of levels of indentation to use
9676 indentLevel int ,
77+ includeField fieldFilter ,
9778) (string , error ) {
9879 out := "\n "
9980
@@ -105,13 +86,7 @@ func CompareResource(
10586 }
10687
10788 // We need a deterministic order to traverse our top-level fields...
108- specFieldNames := []string {}
109- for fieldName := range r .SpecFields {
110- specFieldNames = append (specFieldNames , fieldName )
111- }
112- sort .Strings (specFieldNames )
113-
114- for _ , fieldName := range specFieldNames {
89+ for _ , fieldName := range sortedSpecFieldNames (r ) {
11590 specField := r .SpecFields [fieldName ]
11691 indent := strings .Repeat ("\t " , indentLevel )
11792 firstResAdaptedVarName := firstResVarName + cfg .PrefixConfig .SpecField
@@ -129,7 +104,7 @@ func CompareResource(
129104 compareConfig = fieldConfig .Compare
130105 }
131106
132- if compareConfig != nil && compareConfig . IsIgnored {
107+ if ! includeField ( compareConfig ) {
133108 continue
134109 }
135110
@@ -271,6 +246,84 @@ func CompareResource(
271246 return out , nil
272247}
273248
249+ // CompareResource returns the Go code that traverses a set of two Resources,
250+ // adding differences between the two Resources to an `ackcompare.Delta`
251+ //
252+ // By default, we produce Go code that only looks at the fields in a resource's
253+ // Spec, since those are the fields that represent the desired state of a
254+ // resource. When we make a ReadOne/ReadMany/GetAttributes call to a backend
255+ // AWS API, we construct a Resource and set the Spec fields to values contained
256+ // in the ReadOne/ReadMany/GetAttributes Output shape. This Resource,
257+ // constructed from the Read operation, is compared to the Resource we got from
258+ // the Kubernetes API server's event bus. The code that is returned from this
259+ // function is the code that compares those two Resources.
260+ //
261+ // The Go code we return depends on the Go type of the various fields for the
262+ // resource being compared.
263+ //
264+ // For *scalar* Go types, the output Go code looks like this:
265+ //
266+ // if ackcompare.HasNilDifference(a.ko.Spec.GrantFullControl, b.ko.Spec.GrantFullControl) {
267+ // delta.Add("Spec.GrantFullControl", a.ko.Spec.GrantFullControl, b.ko.Spec.GrantFullControl)
268+ // } else if a.ko.Spec.GrantFullControl != nil && b.ko.Spec.GrantFullControl != nil {
269+ //
270+ // if *a.ko.Spec.GrantFullControl != *b.ko.Spec.GrantFullControl {
271+ // delta.Add("Spec.GrantFullControl", a.ko.Spec.GrantFullControl, b.ko.Spec.GrantFullControl)
272+ // }
273+ // }
274+ //
275+ // For *struct* Go types, the output Go code looks like this (note that it is a
276+ // simple recursive-descent output of all the struct's fields...):
277+ //
278+ // if ackcompare.HasNilDifference(a.ko.Spec.CreateBucketConfiguration, b.ko.Spec.CreateBucketConfiguration) {
279+ // delta.Add("Spec.CreateBucketConfiguration", a.ko.Spec.CreateBucketConfiguration, b.ko.Spec.CreateBucketConfiguration)
280+ // } else if a.ko.Spec.CreateBucketConfiguration != nil && b.ko.Spec.CreateBucketConfiguration != nil {
281+ //
282+ // if ackcompare.HasNilDifference(a.ko.Spec.CreateBucketConfiguration.LocationConstraint, b.ko.Spec.CreateBucketConfiguration.LocationConstraint) {
283+ // delta.Add("Spec.CreateBucketConfiguration.LocationConstraint", a.ko.Spec.CreateBucketConfiguration.LocationConstraint, b.ko.Spec.CreateBucketConfiguration.LocationConstraint)
284+ // } else if a.ko.Spec.CreateBucketConfiguration.LocationConstraint != nil && b.ko.Spec.CreateBucketConfiguration.LocationConstraint != nil {
285+ // if *a.ko.Spec.CreateBucketConfiguration.LocationConstraint != *b.ko.Spec.CreateBucketConfiguration.LocationConstraint {
286+ // delta.Add("Spec.CreateBucketConfiguration.LocationConstraint", a.ko.Spec.CreateBucketConfiguration.LocationConstraint, b.ko.Spec.CreateBucketConfiguration.LocationConstraint)
287+ // }
288+ // }
289+ // }
290+ //
291+ // For *slice of strings* Go types, the output Go code looks like this:
292+ //
293+ // if ackcompare.HasNilDifference(a.ko.Spec.AllowedPublishers, b.ko.Spec.AllowedPublishers) {
294+ // delta.Add("Spec.AllowedPublishers", a.ko.Spec.AllowedPublishers, b.ko.Spec.AllowedPublishers)
295+ // } else if a.ko.Spec.AllowedPublishers != nil && b.ko.Spec.AllowedPublishers != nil {
296+ //
297+ // if !ackcompare.SliceStringPEqual(a.ko.Spec.AllowedPublishers.SigningProfileVersionARNs, b.ko.Spec.AllowedPublishers.SigningProfileVersionARNs) {
298+ // delta.Add("Spec.AllowedPublishers.SigningProfileVersionARNs", a.ko.Spec.AllowedPublishers.SigningProfileVersionARNs, b.ko.Spec.AllowedPublishers.SigningProfileVersionARNs)
299+ // }
300+ // }
301+ func CompareResource (
302+ cfg * ackgenconfig.Config ,
303+ r * model.CRD ,
304+ // String representing the name of the variable that is of type
305+ // `*ackcompare.Delta`. We will generate Go code that calls the `Add()`
306+ // method of this variable when differences between fields are detected.
307+ deltaVarName string ,
308+ // String representing the name of the variable that represents the first
309+ // CR under comparison. This will typically be something like "a.ko". See
310+ // `templates/pkg/resource/delta.go.tpl`.
311+ firstResVarName string ,
312+ // String representing the name of the variable that represents the second
313+ // CR under comparison. This will typically be something like "b.ko". See
314+ // `templates/pkg/resource/delta.go.tpl`.
315+ secondResVarName string ,
316+ // Number of levels of indentation to use
317+ indentLevel int ,
318+ ) (string , error ) {
319+ return compareResourceFields (cfg , r , deltaVarName , firstResVarName , secondResVarName , indentLevel ,
320+ func (compareConfig * ackgenconfig.CompareFieldConfig ) bool {
321+ // Skip fields marked as ignored in normal reconciliation
322+ return compareConfig == nil || ! compareConfig .IsIgnored
323+ },
324+ )
325+ }
326+
274327// compareNil outputs Go code that compares two field values for nullability
275328// and, if there is a nil difference, adds the difference to a variable
276329// representing the `ackcompare.Delta`
@@ -1079,3 +1132,107 @@ func fastCompareTypes(
10791132 }
10801133 return out , needToCloseBlock , nil
10811134}
1135+
1136+ // CompareResourceForPreDelete returns the Go code that traverses a set of two
1137+ // Resources, adding differences between the two Resources to an
1138+ // `ackcompare.Delta`.
1139+ //
1140+ // When the resource has `pre_delete_sync.compare_all: true` set, ALL fields
1141+ // are included (even those with `compare.is_ignored: true`), mirroring
1142+ // CompareResource without the is_ignored skip.
1143+ //
1144+ // Otherwise (the default), only fields explicitly opted in via
1145+ // `compare.pre_delete_include: true` are included; all other fields are
1146+ // skipped. This opt-in approach reduces blast radius so controllers only get
1147+ // pre-delete sync comparison on fields they've deliberately configured.
1148+ func CompareResourceForPreDelete (
1149+ cfg * ackgenconfig.Config ,
1150+ r * model.CRD ,
1151+ // String representing the name of the variable that is of type
1152+ // `*ackcompare.Delta`. We will generate Go code that calls the `Add()`
1153+ // method of this variable when differences between fields are detected.
1154+ deltaVarName string ,
1155+ // String representing the name of the variable that represents the first
1156+ // CR under comparison. This will typically be something like "a.ko". See
1157+ // `templates/pkg/resource/delta.go.tpl`.
1158+ firstResVarName string ,
1159+ // String representing the name of the variable that represents the second
1160+ // CR under comparison. This will typically be something like "b.ko". See
1161+ // `templates/pkg/resource/delta.go.tpl`.
1162+ secondResVarName string ,
1163+ // Number of levels of indentation to use
1164+ indentLevel int ,
1165+ ) (string , error ) {
1166+ resConfig := cfg .GetResourceConfig (r .Names .Camel )
1167+
1168+ // Determine whether to include all fields in the pre-delete sync delta.
1169+ compareAll := false
1170+ if resConfig != nil && resConfig .PreDeleteSync != nil {
1171+ compareAll = resConfig .PreDeleteSync .CompareAll
1172+ }
1173+
1174+ return compareResourceFields (cfg , r , deltaVarName , firstResVarName , secondResVarName , indentLevel ,
1175+ func (compareConfig * ackgenconfig.CompareFieldConfig ) bool {
1176+ if compareAll {
1177+ return true
1178+ }
1179+ // Default opt-in behavior: only include fields with pre_delete_include
1180+ return compareConfig != nil && compareConfig .PreDeleteInclude
1181+ },
1182+ )
1183+ }
1184+
1185+ // MergeResourceForPreDelete outputs Go code that copies only the pre-delete sync
1186+ // configured fields from the source variable onto the target variable. This is
1187+ // used to build a merged resource that starts as a deep copy of observed and
1188+ // has only the pre-delete sync fields overwritten from desired.
1189+ //
1190+ // The generated code is a series of simple field assignments for each field
1191+ // that has `compare.pre_delete_include: true` (or all fields when
1192+ // `pre_delete_sync.compare_all: true`).
1193+ func MergeResourceForPreDelete (
1194+ cfg * ackgenconfig.Config ,
1195+ r * model.CRD ,
1196+ // String representing the variable to copy fields FROM (e.g. "a.ko")
1197+ sourceVarName string ,
1198+ // String representing the variable to copy fields TO (e.g. "merged.ko")
1199+ targetVarName string ,
1200+ // Number of levels of indentation to use
1201+ indentLevel int ,
1202+ ) (string , error ) {
1203+ out := ""
1204+
1205+ resConfig := cfg .GetResourceConfig (r .Names .Camel )
1206+
1207+ compareAll := false
1208+ if resConfig != nil && resConfig .PreDeleteSync != nil {
1209+ compareAll = resConfig .PreDeleteSync .CompareAll
1210+ }
1211+
1212+ for _ , fieldName := range sortedSpecFieldNames (r ) {
1213+ specField := r .SpecFields [fieldName ]
1214+ indent := strings .Repeat ("\t " , indentLevel )
1215+
1216+ var fieldConfig * ackgenconfig.FieldConfig
1217+ var compareConfig * ackgenconfig.CompareFieldConfig
1218+
1219+ if resConfig != nil {
1220+ fieldConfig = resConfig .GetFieldConfig (fieldName )
1221+ }
1222+ if fieldConfig != nil {
1223+ compareConfig = fieldConfig .Compare
1224+ }
1225+
1226+ if ! compareAll {
1227+ if compareConfig == nil || ! compareConfig .PreDeleteInclude {
1228+ continue
1229+ }
1230+ }
1231+
1232+ sourceField := sourceVarName + cfg .PrefixConfig .SpecField + "." + specField .Names .Camel
1233+ targetField := targetVarName + cfg .PrefixConfig .SpecField + "." + specField .Names .Camel
1234+
1235+ out += fmt .Sprintf ("%s%s = %s\n " , indent , targetField , sourceField )
1236+ }
1237+ return out , nil
1238+ }
0 commit comments