@@ -17,7 +17,6 @@ package komega
17
17
import (
18
18
"fmt"
19
19
"reflect"
20
- "strconv"
21
20
"strings"
22
21
23
22
"github.com/google/go-cmp/cmp"
@@ -33,15 +32,15 @@ var (
33
32
// IgnoreAutogeneratedMetadata contains the paths for all the metadata fields that are commonly set by the
34
33
// client and APIServer. This is used as a MatchOption for situations when only user-provided metadata is relevant.
35
34
IgnoreAutogeneratedMetadata = IgnorePaths {
36
- { "ObjectMeta" , "UID" } ,
37
- { "ObjectMeta" , "Generation" } ,
38
- { "ObjectMeta" , "CreationTimestamp" } ,
39
- { "ObjectMeta" , "ResourceVersion" } ,
40
- { "ObjectMeta" , "ManagedFields" } ,
41
- { "ObjectMeta" , "DeletionGracePeriodSeconds" } ,
42
- { "ObjectMeta" , "DeletionTimestamp" } ,
43
- { "ObjectMeta" , "SelfLink" } ,
44
- { "ObjectMeta" , "GenerateName" } ,
35
+ "metadata.uid" ,
36
+ "metadata.generation" ,
37
+ "metadata.creationTimestamp" ,
38
+ "metadata.resourceVersion" ,
39
+ "metadata.managedFields" ,
40
+ "metadata.deletionGracePeriodSeconds" ,
41
+ "metadata.deletionTimestamp" ,
42
+ "metadata.selfLink" ,
43
+ "metadata.generateName" ,
45
44
}
46
45
)
47
46
@@ -112,76 +111,67 @@ func (d diffPath) String() string {
112
111
// diffReporter is a custom recorder for cmp.Diff which records all paths that are
113
112
// different between two objects.
114
113
type diffReporter struct {
115
- stack []cmp.PathStep
116
- path []string
117
- jsonPath []string
114
+ stack []cmp.PathStep
118
115
119
116
diffPaths []diffPath
120
117
}
121
118
122
119
func (r * diffReporter ) PushStep (s cmp.PathStep ) {
123
120
r .stack = append (r .stack , s )
124
- if len (r .stack ) <= 1 {
125
- return
126
- }
127
- switch s := s .(type ) {
128
- case cmp.SliceIndex :
129
- r .path = append (r .path , strconv .Itoa (s .Key ()))
130
- r .jsonPath = append (r .jsonPath , strconv .Itoa (s .Key ()))
131
- case cmp.MapIndex :
132
- key := fmt .Sprintf ("%v" , s .Key ())
133
- // if strings.ContainsAny(key, ".[]/\\") {
134
- // key = fmt.Sprintf("[%s]", key)
135
- // } else {
136
- // key = "." + key
137
- // }
138
- r .path = append (r .path , key )
139
- r .jsonPath = append (r .jsonPath , key )
140
- case cmp.StructField :
141
- field := r .stack [len (r .stack )- 2 ].Type ().Field (s .Index ())
142
- jsonName := strings .Split (field .Tag .Get ("json" ), "," )[0 ]
143
- r .path = append (r .path , s .String ()[1 :])
144
- r .jsonPath = append (r .jsonPath , jsonName )
145
- }
146
121
}
147
122
148
123
func (r * diffReporter ) Report (res cmp.Result ) {
149
124
if ! res .Equal () {
150
- r .diffPaths = append (r .diffPaths , diffPath { types : r . path , json : r . jsonPath } )
125
+ r .diffPaths = append (r .diffPaths , r . currentPath () )
151
126
}
152
127
}
153
128
154
- // func (r *diffReporter) currPath() string {
155
- // p := []string{}
156
- // for _, s := range r.stack[1:] {
157
- // switch s := s.(type) {
158
- // case cmp.StructField, cmp.SliceIndex, cmp.MapIndex:
159
- // p = append(p, s.String())
160
- // }
161
- // }
162
- // return strings.Join(p, "")[1:]
163
- // }
129
+ // currentPath converts the current stack into string representations that match
130
+ // the IgnorePaths and MatchPaths syntax.
131
+ func (r * diffReporter ) currentPath () diffPath {
132
+ p := diffPath {types : []string {"" }, json : []string {"" }}
133
+ for si , s := range r .stack [1 :] {
134
+ switch s := s .(type ) {
135
+ case cmp.StructField :
136
+ p .types = append (p .types , s .String ()[1 :])
137
+ // fetch the type information from the parent struct.
138
+ // Note: si has an offset of 1 compared to r.stack as we loop over r.stack[1:], so we don't need -1
139
+ field := r .stack [si ].Type ().Field (s .Index ())
140
+ p .json = append (p .json , strings .Split (field .Tag .Get ("json" ), "," )[0 ])
141
+ case cmp.SliceIndex :
142
+ key := fmt .Sprintf ("[%d]" , s .Key ())
143
+ p .types [len (p .types )- 1 ] += key
144
+ p .json [len (p .json )- 1 ] += key
145
+ case cmp.MapIndex :
146
+ key := fmt .Sprintf ("%v" , s .Key ())
147
+ if strings .ContainsAny (key , ".[]/\\ " ) {
148
+ key = fmt .Sprintf ("[%s]" , key )
149
+ p .types [len (p .types )- 1 ] += key
150
+ p .json [len (p .json )- 1 ] += key
151
+ } else {
152
+ p .types = append (p .types , key )
153
+ p .json = append (p .json , key )
154
+ }
155
+ }
156
+ }
157
+ // Empty strings were added as the first element. If they're still empty, remove them again.
158
+ if len (p .json ) > 0 && len (p .json [0 ]) == 0 {
159
+ p .json = p .json [1 :]
160
+ p .types = p .types [1 :]
161
+ }
162
+ return p
163
+ }
164
164
165
165
func (r * diffReporter ) PopStep () {
166
- popped := r .stack [len (r .stack )- 1 ]
167
166
r .stack = r .stack [:len (r .stack )- 1 ]
168
- if _ , ok := popped .(cmp.Indirect ); ok {
169
- return
170
- }
171
- if len (r .stack ) <= 1 {
172
- return
173
- }
174
- switch popped .(type ) {
175
- case cmp.SliceIndex , cmp.MapIndex , cmp.StructField :
176
- r .path = r .path [:len (r .path )- 1 ]
177
- r .jsonPath = r .jsonPath [:len (r .jsonPath )- 1 ]
178
- }
179
167
}
180
168
181
169
// calculateDiff calculates the difference between two objects and returns the
182
170
// paths of the fields that do not match.
183
171
func (m * equalObjectMatcher ) calculateDiff (actual interface {}) []diffPath {
184
172
var original interface {} = m .original
173
+ // Remove the wrapping Object from unstructured.Unstructured to make comparison behave similar to
174
+ // regular objects.
185
175
if u , isUnstructured := actual .(* unstructured.Unstructured ); isUnstructured {
186
176
actual = u .Object
187
177
}
@@ -196,33 +186,47 @@ func (m *equalObjectMatcher) calculateDiff(actual interface{}) []diffPath {
196
186
// filterDiffPaths filters the diff paths using the paths in EqualObjectOptions.
197
187
func filterDiffPaths (opts EqualObjectOptions , paths []diffPath ) []diffPath {
198
188
result := []diffPath {}
199
- for _ , c := range paths {
200
- if len (opts .matchPaths ) > 0 && (! matchesAnyPath (c .types , opts .matchPaths ) || ! matchesAnyPath (c .json , opts .matchPaths )) {
189
+
190
+ for _ , p := range paths {
191
+ if len (opts .matchPaths ) > 0 && ! hasAnyPathPrefix (p , opts .matchPaths ) {
201
192
continue
202
193
}
203
- if matchesAnyPath ( c . types , opts . ignorePaths ) || matchesAnyPath ( c . json , opts .ignorePaths ) {
194
+ if hasAnyPathPrefix ( p , opts .ignorePaths ) {
204
195
continue
205
196
}
206
- result = append (result , c )
197
+
198
+ result = append (result , p )
207
199
}
200
+
208
201
return result
209
202
}
210
203
211
- func matchesPath (path []string , prefix []string ) bool {
204
+ // hasPathPrefix compares the segments of a path.
205
+ func hasPathPrefix (path []string , prefix []string ) bool {
212
206
for i , p := range prefix {
213
- if i >= len (path ) || p != path [i ] {
207
+ if i >= len (path ) {
208
+ return false
209
+ }
210
+ // return false if a segment doesn't match
211
+ if path [i ] != p && (i < len (prefix )- 1 || ! segmentHasPrefix (path [i ], p )) {
214
212
return false
215
213
}
216
214
}
217
215
return true
218
216
}
219
217
220
- // matchesAnyPath returns true if path matches any of the path prefixes.
218
+ func segmentHasPrefix (s , prefix string ) bool {
219
+ return len (s ) >= len (prefix ) && s [0 :len (prefix )] == prefix &&
220
+ // if it is a prefix match, make sure the next character is a [ for array/map access
221
+ (len (s ) == len (prefix ) || s [len (prefix )] == '[' )
222
+ }
223
+
224
+ // hasAnyPathPrefix returns true if path matches any of the path prefixes.
221
225
// It respects the name boundaries within paths, so 'ObjectMeta.Name' does not
222
226
// match 'ObjectMeta.Namespace' for example.
223
- func matchesAnyPath (path [] string , prefixes [][]string ) bool {
227
+ func hasAnyPathPrefix (path diffPath , prefixes [][]string ) bool {
224
228
for _ , prefix := range prefixes {
225
- if matchesPath (path , prefix ) {
229
+ if hasPathPrefix (path . types , prefix ) || hasPathPrefix ( path . json , prefix ) {
226
230
return true
227
231
}
228
232
}
@@ -249,23 +253,46 @@ func (o *EqualObjectOptions) ApplyOptions(opts []EqualObjectOption) *EqualObject
249
253
return o
250
254
}
251
255
252
- // func parsePath(path string) []string {
253
- // s := strings.Split(path, ".")
254
- // return s
255
- // }
256
-
257
256
// IgnorePaths instructs the Matcher to ignore given paths when computing a diff.
258
- type IgnorePaths [][]string
257
+ // Paths are written in a syntax similar to Go with a few special cases. Both types and
258
+ // json/yaml field names are supported.
259
+ //
260
+ // Regular Paths
261
+ // "ObjectMeta.Name"
262
+ // "metadata.name"
263
+ // Arrays
264
+ // "metadata.ownerReferences[0].name"
265
+ // Maps, if they do not contain any of .[]/\
266
+ // "metadata.labels.something"
267
+ // Maps, if they contain any of .[]/\
268
+ // "metadata.labels[kubernetes.io/something]"
269
+ type IgnorePaths []string
259
270
260
271
// ApplyToEqualObjectMatcher applies this configuration to the given MatchOptions.
261
272
func (i IgnorePaths ) ApplyToEqualObjectMatcher (opts * EqualObjectOptions ) {
262
- opts .ignorePaths = append (opts .ignorePaths , i ... )
273
+ for _ , p := range i {
274
+ opts .ignorePaths = append (opts .ignorePaths , strings .Split (p , "." ))
275
+ }
263
276
}
264
277
265
278
// MatchPaths instructs the Matcher to restrict its diff to the given paths. If empty the Matcher will look at all paths.
266
- type MatchPaths [][]string
279
+ // Paths are written in a syntax similar to Go with a few special cases. Both types and
280
+ // json/yaml field names are supported.
281
+ //
282
+ // Regular Paths
283
+ // "ObjectMeta.Name"
284
+ // "metadata.name"
285
+ // Arrays
286
+ // "metadata.ownerReferences[0].name"
287
+ // Maps, if they do not contain any of .[]/\
288
+ // "metadata.labels.something"
289
+ // Maps, if they contain any of .[]/\
290
+ // "metadata.labels[kubernetes.io/something]"
291
+ type MatchPaths []string
267
292
268
293
// ApplyToEqualObjectMatcher applies this configuration to the given MatchOptions.
269
294
func (i MatchPaths ) ApplyToEqualObjectMatcher (opts * EqualObjectOptions ) {
270
- opts .matchPaths = append (opts .matchPaths , i ... )
295
+ for _ , p := range i {
296
+ opts .matchPaths = append (opts .ignorePaths , strings .Split (p , "." ))
297
+ }
271
298
}
0 commit comments