1
1
package diff
2
2
3
3
import (
4
+ "maps"
4
5
"reflect"
6
+ "slices"
7
+ "strings"
5
8
6
9
"github.com/google/go-cmp/cmp"
7
- "github.com/google/go-cmp/cmp/cmpopts"
8
10
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9
- "golang.org/x/exp/maps"
10
11
)
11
12
12
13
// SchemaDiff is a nested map with resource names as top-level keys.
@@ -15,8 +16,35 @@ type SchemaDiff map[string]ResourceDiff
15
16
type ResourceDiff struct {
16
17
ResourceConfig ResourceConfigDiff
17
18
Fields map [string ]FieldDiff
19
+ FieldSets ResourceFieldSetsDiff
18
20
}
19
21
22
+ type ResourceFieldSetsDiff struct {
23
+ Old ResourceFieldSets
24
+ New ResourceFieldSets
25
+ }
26
+
27
+ type ResourceFieldSetsDiffWithKeys struct {
28
+ Old ResourceFieldSetsWithKeys
29
+ New ResourceFieldSetsWithKeys
30
+ }
31
+
32
+ type ResourceFieldSets struct {
33
+ ConflictsWith []FieldSet
34
+ ExactlyOneOf []FieldSet
35
+ AtLeastOneOf []FieldSet
36
+ RequiredWith []FieldSet
37
+ }
38
+
39
+ type ResourceFieldSetsWithKeys struct {
40
+ ConflictsWith map [string ]FieldSet
41
+ ExactlyOneOf map [string ]FieldSet
42
+ AtLeastOneOf map [string ]FieldSet
43
+ RequiredWith map [string ]FieldSet
44
+ }
45
+
46
+ type FieldSet map [string ]struct {}
47
+
20
48
type ResourceConfigDiff struct {
21
49
Old * schema.Resource
22
50
New * schema.Resource
@@ -29,7 +57,7 @@ type FieldDiff struct {
29
57
30
58
func ComputeSchemaDiff (oldResourceMap , newResourceMap map [string ]* schema.Resource ) SchemaDiff {
31
59
schemaDiff := make (SchemaDiff )
32
- for resource , _ := range union (maps . Keys ( oldResourceMap ), maps . Keys ( newResourceMap ) ) {
60
+ for resource := range union (oldResourceMap , newResourceMap ) {
33
61
// Compute diff between old and new resources and fields.
34
62
// TODO: add support for computing diff between resource configs, not just whether the
35
63
// resource was added/removed. b/300114839
@@ -47,34 +75,23 @@ func ComputeSchemaDiff(oldResourceMap, newResourceMap map[string]*schema.Resourc
47
75
}
48
76
49
77
resourceDiff .Fields = make (map [string ]FieldDiff )
50
- for key , _ := range union (maps .Keys (flattenedOldSchema ), maps .Keys (flattenedNewSchema )) {
78
+ fieldSetsDiffWithKeys := ResourceFieldSetsDiffWithKeys {}
79
+ for key := range union (flattenedOldSchema , flattenedNewSchema ) {
51
80
oldField := flattenedOldSchema [key ]
52
81
newField := flattenedNewSchema [key ]
53
- if fieldChanged (oldField , newField ) {
54
- resourceDiff .Fields [key ] = FieldDiff {
55
- Old : oldField ,
56
- New : newField ,
57
- }
82
+ if fieldDiff , fieldSetsDiff , changed := diffFields (oldField , newField , key ); changed {
83
+ resourceDiff .Fields [key ] = fieldDiff
84
+ fieldSetsDiffWithKeys = mergeFieldSetsDiff (fieldSetsDiffWithKeys , fieldSetsDiff )
58
85
}
59
86
}
87
+ resourceDiff .FieldSets = removeFieldSetsDiffKeys (fieldSetsDiffWithKeys )
60
88
if len (resourceDiff .Fields ) > 0 || ! cmp .Equal (resourceDiff .ResourceConfig .Old , resourceDiff .ResourceConfig .New ) {
61
89
schemaDiff [resource ] = resourceDiff
62
90
}
63
91
}
64
92
return schemaDiff
65
93
}
66
94
67
- func union (keys1 , keys2 []string ) map [string ]struct {} {
68
- allKeys := make (map [string ]struct {})
69
- for _ , key := range keys1 {
70
- allKeys [key ] = struct {}{}
71
- }
72
- for _ , key := range keys2 {
73
- allKeys [key ] = struct {}{}
74
- }
75
- return allKeys
76
- }
77
-
78
95
func flattenSchema (parentKey string , schemaObj map [string ]* schema.Schema ) map [string ]* schema.Schema {
79
96
flattened := make (map [string ]* schema.Schema )
80
97
@@ -96,16 +113,48 @@ func flattenSchema(parentKey string, schemaObj map[string]*schema.Schema) map[st
96
113
return flattened
97
114
}
98
115
99
- func fieldChanged (oldField , newField * schema.Schema ) bool {
116
+ func diffFields (oldField , newField * schema.Schema , fieldName string ) ( FieldDiff , ResourceFieldSetsDiff , bool ) {
100
117
// If either field is nil, it is changed; if both are nil (which should never happen) it's not
101
118
if oldField == nil && newField == nil {
102
- return false
119
+ return FieldDiff {}, ResourceFieldSetsDiff {}, false
120
+ }
121
+
122
+ oldFieldSets := fieldSets (oldField , fieldName )
123
+ newFieldSets := fieldSets (newField , fieldName )
124
+
125
+ fieldDiff := FieldDiff {
126
+ Old : oldField ,
127
+ New : newField ,
128
+ }
129
+ fieldSetsDiff := ResourceFieldSetsDiff {
130
+ Old : oldFieldSets ,
131
+ New : newFieldSets ,
103
132
}
104
133
if oldField == nil || newField == nil {
105
- return true
134
+ return fieldDiff , fieldSetsDiff , true
106
135
}
107
136
// Check if any basic Schema struct fields have changed.
108
137
// https://github.com/hashicorp/terraform-plugin-sdk/blob/v2.24.0/helper/schema/schema.go#L44
138
+ if basicSchemaChanged (oldField , newField ) {
139
+ return fieldDiff , fieldSetsDiff , true
140
+ }
141
+
142
+ if ! cmp .Equal (oldFieldSets , newFieldSets ) {
143
+ return fieldDiff , fieldSetsDiff , true
144
+ }
145
+
146
+ if elemChanged (oldField , newField ) {
147
+ return fieldDiff , fieldSetsDiff , true
148
+ }
149
+
150
+ if funcsChanged (oldField , newField ) {
151
+ return fieldDiff , fieldSetsDiff , true
152
+ }
153
+
154
+ return FieldDiff {}, ResourceFieldSetsDiff {}, false
155
+ }
156
+
157
+ func basicSchemaChanged (oldField , newField * schema.Schema ) bool {
109
158
if oldField .Type != newField .Type {
110
159
return true
111
160
}
@@ -148,26 +197,35 @@ func fieldChanged(oldField, newField *schema.Schema) bool {
148
197
if oldField .Sensitive != newField .Sensitive {
149
198
return true
150
199
}
200
+ return false
201
+ }
151
202
152
- // Compare slices
153
- less := func (a , b string ) bool { return a < b }
154
-
155
- if (len (oldField .ConflictsWith ) > 0 || len (newField .ConflictsWith ) > 0 ) && ! cmp .Equal (oldField .ConflictsWith , newField .ConflictsWith , cmpopts .SortSlices (less )) {
156
- return true
203
+ func fieldSets (field * schema.Schema , fieldName string ) ResourceFieldSets {
204
+ if field == nil {
205
+ return ResourceFieldSets {}
157
206
}
158
-
159
- if ( len (oldField . ExactlyOneOf ) > 0 || len ( newField . ExactlyOneOf ) > 0 ) && ! cmp . Equal ( oldField . ExactlyOneOf , newField . ExactlyOneOf , cmpopts . SortSlices ( less )) {
160
- return true
207
+ var conflictsWith , exactlyOneOf , atLeastOneOf , requiredWith [] FieldSet
208
+ if len (field . ConflictsWith ) > 0 {
209
+ conflictsWith = [] FieldSet { sliceToSetRemoveZeroPadding ( append ( field . ConflictsWith , fieldName ))}
161
210
}
162
-
163
- if (len (oldField .AtLeastOneOf ) > 0 || len (newField .AtLeastOneOf ) > 0 ) && ! cmp .Equal (oldField .AtLeastOneOf , newField .AtLeastOneOf , cmpopts .SortSlices (less )) {
164
- return true
211
+ if len (field .ExactlyOneOf ) > 0 {
212
+ exactlyOneOf = []FieldSet {sliceToSetRemoveZeroPadding (append (field .ExactlyOneOf , fieldName ))}
165
213
}
166
-
167
- if (len (oldField .RequiredWith ) > 0 || len (newField .RequiredWith ) > 0 ) && ! cmp .Equal (oldField .RequiredWith , newField .RequiredWith , cmpopts .SortSlices (less )) {
168
- return true
214
+ if len (field .AtLeastOneOf ) > 0 {
215
+ atLeastOneOf = []FieldSet {sliceToSetRemoveZeroPadding (append (field .AtLeastOneOf , fieldName ))}
216
+ }
217
+ if len (field .RequiredWith ) > 0 {
218
+ requiredWith = []FieldSet {sliceToSetRemoveZeroPadding (append (field .RequiredWith , fieldName ))}
169
219
}
220
+ return ResourceFieldSets {
221
+ ConflictsWith : conflictsWith ,
222
+ ExactlyOneOf : exactlyOneOf ,
223
+ AtLeastOneOf : atLeastOneOf ,
224
+ RequiredWith : requiredWith ,
225
+ }
226
+ }
170
227
228
+ func elemChanged (oldField , newField * schema.Schema ) bool {
171
229
// Check if Elem changed (unless old and new both represent nested fields)
172
230
if (oldField .Elem == nil && newField .Elem != nil ) || (oldField .Elem != nil && newField .Elem == nil ) {
173
231
return true
@@ -183,12 +241,15 @@ func fieldChanged(oldField, newField *schema.Schema) bool {
183
241
return true
184
242
}
185
243
if ! oldIsResource && ! newIsResource {
186
- if fieldChanged (oldField .Elem .(* schema.Schema ), newField .Elem .(* schema.Schema )) {
244
+ if _ , _ , changed := diffFields (oldField .Elem .(* schema.Schema ), newField .Elem .(* schema.Schema ), "" ); changed {
187
245
return true
188
246
}
189
247
}
190
248
}
249
+ return false
250
+ }
191
251
252
+ func funcsChanged (oldField , newField * schema.Schema ) bool {
192
253
// Check if any Schema struct fields that are functions have changed
193
254
if funcChanged (oldField .DiffSuppressFunc , newField .DiffSuppressFunc ) {
194
255
return true
@@ -208,7 +269,6 @@ func fieldChanged(oldField, newField *schema.Schema) bool {
208
269
if funcChanged (oldField .ValidateDiagFunc , newField .ValidateDiagFunc ) {
209
270
return true
210
271
}
211
-
212
272
return false
213
273
}
214
274
@@ -225,3 +285,52 @@ func funcChanged(oldFunc, newFunc interface{}) bool {
225
285
// b/300157205
226
286
return false
227
287
}
288
+
289
+ func mergeFieldSetsDiff (allFields ResourceFieldSetsDiffWithKeys , currentField ResourceFieldSetsDiff ) ResourceFieldSetsDiffWithKeys {
290
+ allFields .Old = mergeResourceFieldSets (allFields .Old , currentField .Old )
291
+ allFields .New = mergeResourceFieldSets (allFields .New , currentField .New )
292
+ return allFields
293
+ }
294
+
295
+ func mergeResourceFieldSets (allFields ResourceFieldSetsWithKeys , currentField ResourceFieldSets ) ResourceFieldSetsWithKeys {
296
+ allFields .ConflictsWith = mergeFieldSets (allFields .ConflictsWith , currentField .ConflictsWith )
297
+ allFields .ExactlyOneOf = mergeFieldSets (allFields .ExactlyOneOf , currentField .ExactlyOneOf )
298
+ allFields .AtLeastOneOf = mergeFieldSets (allFields .AtLeastOneOf , currentField .AtLeastOneOf )
299
+ allFields .RequiredWith = mergeFieldSets (allFields .RequiredWith , currentField .RequiredWith )
300
+ return allFields
301
+ }
302
+
303
+ func mergeFieldSets (allFields map [string ]FieldSet , currentField []FieldSet ) map [string ]FieldSet {
304
+ if allFields == nil {
305
+ allFields = make (map [string ]FieldSet )
306
+ }
307
+ for _ , fieldSet := range currentField {
308
+ allFields [setKey (fieldSet )] = fieldSet
309
+ }
310
+ return allFields
311
+ }
312
+
313
+ func setKey (set FieldSet ) string {
314
+ slice := setToSortedSlice (set )
315
+ return strings .Join (slice , "," )
316
+ }
317
+
318
+ func removeFieldSetsDiffKeys (fieldSets ResourceFieldSetsDiffWithKeys ) ResourceFieldSetsDiff {
319
+ return ResourceFieldSetsDiff {
320
+ Old : removeFieldSetsKey (fieldSets .Old ),
321
+ New : removeFieldSetsKey (fieldSets .New ),
322
+ }
323
+ }
324
+
325
+ func removeFieldSetsKey (fieldSets ResourceFieldSetsWithKeys ) ResourceFieldSets {
326
+ return ResourceFieldSets {
327
+ ConflictsWith : removeFieldSetKey (fieldSets .ConflictsWith ),
328
+ ExactlyOneOf : removeFieldSetKey (fieldSets .ExactlyOneOf ),
329
+ AtLeastOneOf : removeFieldSetKey (fieldSets .AtLeastOneOf ),
330
+ RequiredWith : removeFieldSetKey (fieldSets .RequiredWith ),
331
+ }
332
+ }
333
+
334
+ func removeFieldSetKey (fieldSets map [string ]FieldSet ) []FieldSet {
335
+ return slices .Collect (maps .Values (fieldSets ))
336
+ }
0 commit comments