@@ -27,6 +27,7 @@ import (
2727 . "sigs.k8s.io/structured-merge-diff/v3/internal/fixture"
2828 "sigs.k8s.io/structured-merge-diff/v3/merge"
2929 "sigs.k8s.io/structured-merge-diff/v3/typed"
30+ "sigs.k8s.io/structured-merge-diff/v3/value"
3031)
3132
3233func TestMultipleAppliersSet (t * testing.T ) {
@@ -243,6 +244,300 @@ func TestMultipleAppliersSet(t *testing.T) {
243244 }
244245}
245246
247+ var structMultiversionParser = func () Parser {
248+ parser , err := typed .NewParser (`types:
249+ - name: v1
250+ map:
251+ fields:
252+ - name: struct
253+ type:
254+ namedType: struct
255+ - name: version
256+ type:
257+ scalar: string
258+ - name: struct
259+ map:
260+ fields:
261+ - name: name
262+ type:
263+ scalar: string
264+ - name: scalarField_v1
265+ type:
266+ scalar: string
267+ - name: complexField_v1
268+ type:
269+ namedType: complex
270+ - name: complex
271+ map:
272+ fields:
273+ - name: name
274+ type:
275+ scalar: string
276+ - name: v2
277+ map:
278+ fields:
279+ - name: struct
280+ type:
281+ namedType: struct_v2
282+ - name: version
283+ type:
284+ scalar: string
285+ - name: struct_v2
286+ map:
287+ fields:
288+ - name: name
289+ type:
290+ scalar: string
291+ - name: scalarField_v2
292+ type:
293+ scalar: string
294+ - name: complexField_v2
295+ type:
296+ namedType: complex_v2
297+ - name: complex_v2
298+ map:
299+ fields:
300+ - name: name
301+ type:
302+ scalar: string
303+ - name: v3
304+ map:
305+ fields:
306+ - name: struct
307+ type:
308+ namedType: struct_v3
309+ - name: version
310+ type:
311+ scalar: string
312+ - name: struct_v3
313+ map:
314+ fields:
315+ - name: name
316+ type:
317+ scalar: string
318+ - name: scalarField_v3
319+ type:
320+ scalar: string
321+ - name: complexField_v3
322+ type:
323+ namedType: complex_v3
324+ - name: complex_v3
325+ map:
326+ fields:
327+ - name: name
328+ type:
329+ scalar: string
330+ ` )
331+ if err != nil {
332+ panic (err )
333+ }
334+ return parser
335+ }()
336+
337+ func TestMultipleAppliersFieldUnsetting (t * testing.T ) {
338+ versions := []fieldpath.APIVersion {"v1" , "v2" , "v3" }
339+ for _ , v1 := range versions {
340+ for _ , v2 := range versions {
341+ for _ , v3 := range versions {
342+ t .Run (fmt .Sprintf ("%s-%s-%s" , v1 , v2 , v3 ), func (t * testing.T ) {
343+ testMultipleAppliersFieldUnsetting (t , v1 , v2 , v3 )
344+ })
345+ }
346+ }
347+ }
348+ }
349+
350+ func testMultipleAppliersFieldUnsetting (t * testing.T , v1 , v2 , v3 fieldpath.APIVersion ) {
351+ tests := map [string ]TestCase {
352+ "unset_scalar_sole_owner" : {
353+ Ops : []Operation {
354+ Apply {
355+ Manager : "apply-one" ,
356+ APIVersion : v1 ,
357+ Object : typed .YAMLObject (fmt .Sprintf (`
358+ struct:
359+ name: a
360+ scalarField_%s: a
361+ ` , v1 )),
362+ },
363+ Apply {
364+ Manager : "apply-one" ,
365+ APIVersion : v2 ,
366+ Object : `
367+ struct:
368+ name: a
369+ ` ,
370+ },
371+ },
372+ Object : `
373+ struct:
374+ name: a
375+ ` ,
376+ APIVersion : v3 ,
377+ Managed : fieldpath.ManagedFields {
378+ "apply-one" : fieldpath .NewVersionedSet (
379+ _NS (
380+ _P ("struct" , "name" ),
381+ ),
382+ v2 ,
383+ false ,
384+ ),
385+ },
386+ },
387+ "unset_scalar_shared_owner" : {
388+ Ops : []Operation {
389+ Apply {
390+ Manager : "apply-one" ,
391+ APIVersion : v1 ,
392+ Object : typed .YAMLObject (fmt .Sprintf (`
393+ struct:
394+ name: a
395+ scalarField_%s: a
396+ ` , v1 )),
397+ },
398+ Apply {
399+ Manager : "apply-two" ,
400+ APIVersion : v2 ,
401+ Object : typed .YAMLObject (fmt .Sprintf (`
402+ struct:
403+ scalarField_%s: a
404+ ` , v2 )),
405+ },
406+ Apply {
407+ Manager : "apply-one" ,
408+ APIVersion : v3 ,
409+ Object : `
410+ struct:
411+ name: a
412+ ` ,
413+ },
414+ },
415+ Object : typed .YAMLObject (fmt .Sprintf (`
416+ struct:
417+ name: a
418+ scalarField_%s: a
419+ ` , v3 )),
420+ APIVersion : v3 ,
421+ Managed : fieldpath.ManagedFields {
422+ "apply-one" : fieldpath .NewVersionedSet (
423+ _NS (
424+ _P ("struct" , "name" ),
425+ ),
426+ v3 ,
427+ true ,
428+ ),
429+ "apply-two" : fieldpath .NewVersionedSet (
430+ _NS (
431+ _P ("struct" , fmt .Sprintf ("scalarField_%s" , v2 )),
432+ ),
433+ v2 ,
434+ false ,
435+ ),
436+ },
437+ },
438+ "unset_complex_sole_owner" : {
439+ Ops : []Operation {
440+ Apply {
441+ Manager : "apply-one" ,
442+ APIVersion : v1 ,
443+ Object : typed .YAMLObject (fmt .Sprintf (`
444+ struct:
445+ name: a
446+ complexField_%s:
447+ name: b
448+ ` , v1 )),
449+ },
450+ Apply {
451+ Manager : "apply-one" ,
452+ APIVersion : v2 ,
453+ Object : `
454+ struct:
455+ name: a
456+ ` ,
457+ },
458+ },
459+ Object : typed .YAMLObject (fmt .Sprintf (`
460+ struct:
461+ name: a
462+ complexField_%s: null
463+ ` , v3 )),
464+ APIVersion : v3 ,
465+ Managed : fieldpath.ManagedFields {
466+ "apply-one" : fieldpath .NewVersionedSet (
467+ _NS (
468+ _P ("struct" , "name" ),
469+ ),
470+ v2 ,
471+ false ,
472+ ),
473+ },
474+ },
475+ "unset_complex_shared_owner" : {
476+ Ops : []Operation {
477+ Apply {
478+ Manager : "apply-one" ,
479+ APIVersion : v1 ,
480+ Object : typed .YAMLObject (fmt .Sprintf (`
481+ struct:
482+ name: a
483+ complexField_%s:
484+ name: b
485+ ` , v1 )),
486+ },
487+ Apply {
488+ Manager : "apply-two" ,
489+ APIVersion : v2 ,
490+ Object : typed .YAMLObject (fmt .Sprintf (`
491+ struct:
492+ complexField_%s:
493+ name: b
494+ ` , v2 )),
495+ },
496+ Apply {
497+ Manager : "apply-one" ,
498+ APIVersion : v3 ,
499+ Object : `
500+ struct:
501+ name: a
502+ ` ,
503+ },
504+ },
505+ Object : typed .YAMLObject (fmt .Sprintf (`
506+ struct:
507+ name: a
508+ complexField_%s:
509+ name: b
510+ ` , v3 )),
511+ APIVersion : v3 ,
512+ Managed : fieldpath.ManagedFields {
513+ "apply-one" : fieldpath .NewVersionedSet (
514+ _NS (
515+ _P ("struct" , "name" ),
516+ ),
517+ v3 ,
518+ false ,
519+ ),
520+ "apply-two" : fieldpath .NewVersionedSet (
521+ _NS (
522+ _P ("struct" , fmt .Sprintf ("complexField_%s" , v2 ), "name" ),
523+ ),
524+ v2 ,
525+ false ,
526+ ),
527+ },
528+ },
529+ }
530+
531+ converter := renamingConverter {structMultiversionParser }
532+ for name , test := range tests {
533+ t .Run (name , func (t * testing.T ) {
534+ if err := test .TestWithConverter (structMultiversionParser , converter ); err != nil {
535+ t .Fatal (err )
536+ }
537+ })
538+ }
539+ }
540+
246541func TestMultipleAppliersNestedType (t * testing.T ) {
247542 tests := map [string ]TestCase {
248543 "remove_one_keep_one_with_two_sub_items" : {
@@ -1041,6 +1336,53 @@ func (r repeatingConverter) IsMissingVersionError(err error) bool {
10411336 return err == missingVersionError
10421337}
10431338
1339+ // renamingConverter renames fields by substituting the version suffix of the field name. E.g.
1340+ // converting a map with a field named "name_v1" from v1 to v2 renames the field to "name_v2".
1341+ // Fields without a version suffix are not converted; they are the same in all versions.
1342+ // When parsing, this converter will look for the type by using the APIVersion of the
1343+ // object it's trying to parse. If trying to parse a "v1" object, a corresponding "v1" type
1344+ // should exist in the schema of the provided parser.
1345+ type renamingConverter struct {
1346+ parser Parser
1347+ }
1348+
1349+ // Convert implements merge.Converter
1350+ func (r renamingConverter ) Convert (v * typed.TypedValue , version fieldpath.APIVersion ) (* typed.TypedValue , error ) {
1351+ inVersion := fieldpath .APIVersion (* v .TypeRef ().NamedType )
1352+ outType := r .parser .Type (string (version ))
1353+ return outType .FromUnstructured (renameFields (v .AsValue (), string (inVersion ), string (version )))
1354+ }
1355+
1356+ func renameFields (v value.Value , oldSuffix , newSuffix string ) interface {} {
1357+ if v .IsMap () {
1358+ out := map [string ]interface {}{}
1359+ v .AsMap ().Iterate (func (key string , value value.Value ) bool {
1360+ if strings .HasSuffix (key , oldSuffix ) {
1361+ out [strings .TrimSuffix (key , oldSuffix )+ newSuffix ] = renameFields (value , oldSuffix , newSuffix )
1362+ } else {
1363+ out [key ] = renameFields (value , oldSuffix , newSuffix )
1364+ }
1365+ return true
1366+ })
1367+ return out
1368+ }
1369+ if v .IsList () {
1370+ var out []interface {}
1371+ ri := v .AsList ().Range ()
1372+ for ri .Next () {
1373+ _ , v := ri .Item ()
1374+ out = append (out , renameFields (v , oldSuffix , newSuffix ))
1375+ }
1376+ return out
1377+ }
1378+ return v .Unstructured ()
1379+ }
1380+
1381+ // Convert implements merge.Converter
1382+ func (r renamingConverter ) IsMissingVersionError (err error ) bool {
1383+ return err == missingVersionError
1384+ }
1385+
10441386func BenchmarkMultipleApplierRecursiveRealConversion (b * testing.B ) {
10451387 test := TestCase {
10461388 Ops : []Operation {
0 commit comments