@@ -24,7 +24,6 @@ import (
24
24
25
25
"k8s.io/apimachinery/pkg/util/json"
26
26
"k8s.io/apimachinery/pkg/util/mergepatch"
27
- "k8s.io/apimachinery/pkg/util/sets"
28
27
forkedjson "k8s.io/apimachinery/third_party/forked/golang/json"
29
28
)
30
29
@@ -44,10 +43,10 @@ const (
44
43
replaceDirective = "replace"
45
44
mergeDirective = "merge"
46
45
47
- replaceKeysStrategy = "replaceKeys "
46
+ retainKeysStrategy = "retainKeys "
48
47
49
48
deleteFromPrimitiveListDirectivePrefix = "$deleteFromPrimitiveList"
50
- replaceKeysDirective = "$" + replaceKeysStrategy
49
+ retainKeysDirective = "$" + retainKeysStrategy
51
50
)
52
51
53
52
// JSONMap is a representations of JSON object encoded as map[string]interface{}
@@ -62,16 +61,20 @@ type DiffOptions struct {
62
61
IgnoreChangesAndAdditions bool
63
62
// IgnoreDeletions indicates if we keep the deletions in the patch.
64
63
IgnoreDeletions bool
65
- // We introduce a new value replaceKeys for patchStrategy.
64
+ // We introduce a new value retainKeys for patchStrategy.
66
65
// It indicates that all fields needing to be preserved must be
67
- // present in the `replaceKeys ` list.
66
+ // present in the `retainKeys ` list.
68
67
// And the fields that are present will be merged with live object.
69
68
// All the missing fields will be cleared when patching.
70
- ReplaceKeys bool
69
+ BuildRetainKeysDirective bool
71
70
}
72
71
73
72
type MergeOptions struct {
74
73
// MergeParallelList indicates if we are merging the parallel list.
74
+ // We don't merge parallel list when calling mergeMap() in CreateThreeWayMergePatch()
75
+ // which is called client-side.
76
+ // We merge parallel list iff when calling mergeMap() in StrategicMergeMapPatch()
77
+ // which is called server-side
75
78
MergeParallelList bool
76
79
// IgnoreUnmatchedNulls indicates if we should process the unmatched nulls.
77
80
IgnoreUnmatchedNulls bool
@@ -134,18 +137,29 @@ func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{
134
137
}
135
138
136
139
// Returns a (recursive) strategic merge patch that yields modified when applied to original.
140
+ // Including:
141
+ // - Adding fields to the patch present in modified, missing from original
142
+ // - Setting fields to the patch present in modified and original with different values
143
+ // - Delete fields present in original, missing from modified through
144
+ // - IFF map field - set to nil in patch
145
+ // - IFF list of maps && merge strategy - use deleteDirective for the elements
146
+ // - IFF list of primitives && merge strategy - use parallel deletion list
147
+ // - IFF list of maps or primitives with replace strategy (default) - set patch value to the value in modified
148
+ // - Build $retainKeys directive for fields with retainKeys patch strategy
137
149
func diffMaps (original , modified map [string ]interface {}, t reflect.Type , diffOptions DiffOptions ) (map [string ]interface {}, error ) {
138
150
patch := map [string ]interface {}{}
151
+ // Get the underlying type for pointers
139
152
if t .Kind () == reflect .Ptr {
140
153
t = t .Elem ()
141
154
}
142
- // replaceKeysList will contains all the keys with non-nil value.
143
- replaceKeysList := make ([]interface {}, 0 , len (modified ))
155
+ // This will be used to build the $retainKeys directive sent in the patch
156
+ retainKeysList := make ([]interface {}, 0 , len (modified ))
144
157
158
+ // Compare each value in the modified map against the value in the original map
145
159
for key , modifiedValue := range modified {
146
- // Collect all keys with non-nil value.
147
- if diffOptions .ReplaceKeys && modifiedValue != nil {
148
- replaceKeysList = append (replaceKeysList , key )
160
+ // Get the underlying type for pointers
161
+ if diffOptions .BuildRetainKeysDirective && modifiedValue != nil {
162
+ retainKeysList = append (retainKeysList , key )
149
163
}
150
164
151
165
originalValue , ok := original [key ]
@@ -158,6 +172,7 @@ func diffMaps(original, modified map[string]interface{}, t reflect.Type, diffOpt
158
172
}
159
173
160
174
// The patch may have a patch directive
175
+ // TODO: figure out if we need this. This shouldn't be needed by apply. When would the original map have patch directives in it?
161
176
foundDirectiveMarker , err := handleDirectiveMarker (key , originalValue , modifiedValue , patch )
162
177
if err != nil {
163
178
return nil , err
@@ -191,12 +206,13 @@ func diffMaps(original, modified map[string]interface{}, t reflect.Type, diffOpt
191
206
}
192
207
193
208
updatePatchIfMissing (original , modified , patch , diffOptions )
194
- // Insert the replaceKeysList when either of the following is true:
195
- // - there are changes in patch list
209
+ // Insert the retainKeysList iff there are values present in the retainKeysList and
210
+ // either of the following is true:
211
+ // - the patch is not empty
196
212
// - there are additional field in original that need to be cleared
197
- if len (replaceKeysList ) > 0 &&
213
+ if len (retainKeysList ) > 0 &&
198
214
(len (patch ) > 0 || hasAdditionalNewField (original , modified )) {
199
- patch [replaceKeysDirective ] = sortScalars (replaceKeysList )
215
+ patch [retainKeysDirective ] = sortScalars (retainKeysList )
200
216
}
201
217
return patch , nil
202
218
}
@@ -238,11 +254,11 @@ func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]in
238
254
// Otherwise, return the error
239
255
return err
240
256
}
241
- replaceKeys , patchStrategy , err := extractReplaceKeysPatchStrategy (fieldPatchStrategies )
257
+ retainKeys , patchStrategy , err := extractRetainKeysPatchStrategy (fieldPatchStrategies )
242
258
if err != nil {
243
259
return err
244
260
}
245
- diffOptions .ReplaceKeys = replaceKeys
261
+ diffOptions .BuildRetainKeysDirective = retainKeys
246
262
switch patchStrategy {
247
263
// The patch strategic from metadata tells us to replace the entire object instead of diffing it
248
264
case replaceDirective :
@@ -280,14 +296,14 @@ func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, pat
280
296
// Otherwise, return the error
281
297
return err
282
298
}
283
- replaceKeys , patchStrategy , err := extractReplaceKeysPatchStrategy (fieldPatchStrategies )
299
+ retainKeys , patchStrategy , err := extractRetainKeysPatchStrategy (fieldPatchStrategies )
284
300
if err != nil {
285
301
return err
286
302
}
287
303
switch patchStrategy {
288
304
// Merge the 2 slices using mergePatchKey
289
305
case mergeDirective :
290
- diffOptions .ReplaceKeys = replaceKeys
306
+ diffOptions .BuildRetainKeysDirective = retainKeys
291
307
addList , deletionList , err := diffLists (originalValue , modifiedValue , fieldType .Elem (), fieldPatchMergeKey , diffOptions )
292
308
if err != nil {
293
309
return err
@@ -601,6 +617,7 @@ func getTagStructType(dataStruct interface{}) (reflect.Type, error) {
601
617
}
602
618
603
619
t := reflect .TypeOf (dataStruct )
620
+ // Get the underlying type for pointers
604
621
if t .Kind () == reflect .Ptr {
605
622
t = t .Elem ()
606
623
}
@@ -652,47 +669,53 @@ func preprocessDeletionListForMerging(key string, original map[string]interface{
652
669
return false , false , "" , nil
653
670
}
654
671
655
- // processReplaceKeys processes replaceKeys list.
656
- // When MergeParallelList is true, we validate the patch and
657
- // then clear the fields that are not in the list.
658
- func processReplaceKeys (original , patch map [string ]interface {}, options MergeOptions ) error {
659
- replaceKeysInPatch , foundInPatch := patch [replaceKeysDirective ]
672
+ // applyRetainKeysDirective looks for a retainKeys directive and applies to original
673
+ // - if no directive exists do nothing
674
+ // - if directive is found, clear keys in original missing from the directive list
675
+ // - validate that all keys present in the patch are present in the retainKeys directive
676
+ // note: original may be another patch request, e.g. applying the add+modified patch to the deletions patch. In this case it may have directives
677
+ func applyRetainKeysDirective (original , patch map [string ]interface {}, options MergeOptions ) error {
678
+ retainKeysInPatch , foundInPatch := patch [retainKeysDirective ]
660
679
if ! foundInPatch {
661
680
return nil
662
681
}
663
- delete (patch , replaceKeysDirective )
682
+ // cleanup the directive
683
+ delete (patch , retainKeysDirective )
664
684
665
685
if ! options .MergeParallelList {
666
- replaceKeysInOriginal , foundInOriginal := original [replaceKeysDirective ]
686
+ // If original is actually a patch, make sure the retainKeys directives are the same in both patches if present in both.
687
+ // If not present in the original patch, copy from the modified patch.
688
+ retainKeysInOriginal , foundInOriginal := original [retainKeysDirective ]
667
689
if foundInOriginal {
668
- if ! reflect .DeepEqual (replaceKeysInOriginal , replaceKeysInPatch ) {
690
+ if ! reflect .DeepEqual (retainKeysInOriginal , retainKeysInPatch ) {
669
691
// This error actually should never happen.
670
- return fmt .Errorf ("%v and %v are not deep equal: this may happen when calculating the 3-way diff patch" , replaceKeysInOriginal , replaceKeysInPatch )
692
+ return fmt .Errorf ("%v and %v are not deep equal: this may happen when calculating the 3-way diff patch" , retainKeysInOriginal , retainKeysInPatch )
671
693
}
672
694
} else {
673
- original [replaceKeysDirective ] = replaceKeysInPatch
695
+ original [retainKeysDirective ] = retainKeysInPatch
674
696
}
675
697
return nil
676
698
}
677
699
678
- replaceKeysList , ok := replaceKeysInPatch .([]interface {})
700
+ retainKeysList , ok := retainKeysInPatch .([]interface {})
679
701
if ! ok {
680
- return mergepatch .ErrBadPatchFormatForReplaceKeys
702
+ return mergepatch .ErrBadPatchFormatForRetainKeys
681
703
}
682
704
683
- // validate patch to make sure all field in patch is covered in replaceKeysList
684
- m := map [interface {}]sets.Empty {}
685
- for _ , v := range replaceKeysList {
686
- m [v ] = sets.Empty {}
705
+ // validate patch to make sure all fields in the patch are present in the retainKeysList.
706
+ // The map is used only as a set, the value is never referenced
707
+ m := map [interface {}]struct {}{}
708
+ for _ , v := range retainKeysList {
709
+ m [v ] = struct {}{}
687
710
}
688
711
for k , v := range patch {
689
712
if v == nil || strings .HasPrefix (k , deleteFromPrimitiveListDirectivePrefix ) {
690
713
continue
691
714
}
692
- // If there is an item present in the patch but not in the replaceKeys list,
715
+ // If there is an item present in the patch but not in the retainKeys list,
693
716
// the patch is invalid.
694
717
if _ , found := m [k ]; ! found {
695
- return mergepatch .ErrPatchContentNotMatchReplaceKeys
718
+ return mergepatch .ErrBadPatchFormatForRetainKeys
696
719
}
697
720
}
698
721
@@ -723,7 +746,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio
723
746
original = map [string ]interface {}{}
724
747
}
725
748
726
- err := processReplaceKeys (original , patch , mergeOptions )
749
+ err := applyRetainKeysDirective (original , patch , mergeOptions )
727
750
if err != nil {
728
751
return nil , err
729
752
}
@@ -778,7 +801,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio
778
801
if err != nil {
779
802
return nil , err
780
803
}
781
- _ , patchStrategy , err := extractReplaceKeysPatchStrategy (fieldPatchStrategies )
804
+ _ , patchStrategy , err := extractRetainKeysPatchStrategy (fieldPatchStrategies )
782
805
if err != nil {
783
806
return nil , err
784
807
}
@@ -1028,10 +1051,10 @@ func sortMergeListsByName(mapJSON []byte, dataStruct interface{}) ([]byte, error
1028
1051
func sortMergeListsByNameMap (s map [string ]interface {}, t reflect.Type ) (map [string ]interface {}, error ) {
1029
1052
newS := map [string ]interface {}{}
1030
1053
for k , v := range s {
1031
- if k == replaceKeysDirective {
1054
+ if k == retainKeysDirective {
1032
1055
typedV , ok := v .([]interface {})
1033
1056
if ! ok {
1034
- return nil , mergepatch .ErrBadPatchFormatForReplaceKeys
1057
+ return nil , mergepatch .ErrBadPatchFormatForRetainKeys
1035
1058
}
1036
1059
v = sortScalars (typedV )
1037
1060
} else if strings .HasPrefix (k , deleteFromPrimitiveListDirectivePrefix ) {
@@ -1040,13 +1063,12 @@ func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[stri
1040
1063
return nil , mergepatch .ErrBadPatchFormatForPrimitiveList
1041
1064
}
1042
1065
v = uniqifyAndSortScalars (typedV )
1043
- } else if k != directiveMarker && k != replaceKeysDirective &&
1044
- ! strings .HasPrefix (k , deleteFromPrimitiveListDirectivePrefix ) {
1066
+ } else if k != directiveMarker {
1045
1067
fieldType , fieldPatchStrategies , fieldPatchMergeKey , err := forkedjson .LookupPatchMetadata (t , k )
1046
1068
if err != nil {
1047
1069
return nil , err
1048
1070
}
1049
- _ , patchStrategy , err := extractReplaceKeysPatchStrategy (fieldPatchStrategies )
1071
+ _ , patchStrategy , err := extractRetainKeysPatchStrategy (fieldPatchStrategies )
1050
1072
if err != nil {
1051
1073
return nil , err
1052
1074
}
@@ -1305,13 +1327,13 @@ func mergingMapFieldsHaveConflicts(
1305
1327
1306
1328
func mapsHaveConflicts (typedLeft , typedRight map [string ]interface {}, structType reflect.Type ) (bool , error ) {
1307
1329
for key , leftValue := range typedLeft {
1308
- if key != directiveMarker && key != replaceKeysDirective {
1330
+ if key != directiveMarker && key != retainKeysDirective {
1309
1331
if rightValue , ok := typedRight [key ]; ok {
1310
1332
fieldType , fieldPatchStrategies , fieldPatchMergeKey , err := forkedjson .LookupPatchMetadata (structType , key )
1311
1333
if err != nil {
1312
1334
return true , err
1313
1335
}
1314
- _ , patchStrategy , err := extractReplaceKeysPatchStrategy (fieldPatchStrategies )
1336
+ _ , patchStrategy , err := extractRetainKeysPatchStrategy (fieldPatchStrategies )
1315
1337
if err != nil {
1316
1338
return true , err
1317
1339
}
@@ -1539,26 +1561,26 @@ func sliceTypeAssertion(original, patch interface{}) ([]interface{}, []interface
1539
1561
return typedOriginal , typedPatch , nil
1540
1562
}
1541
1563
1542
- // extractReplaceKeysPatchStrategy process patch strategy, which is a string may contains multiple
1564
+ // extractRetainKeysPatchStrategy process patch strategy, which is a string may contains multiple
1543
1565
// patch strategies seperated by ",". It returns a boolean var indicating if it has
1544
- // replaceKeys strategies and a string for the other strategy.
1545
- func extractReplaceKeysPatchStrategy (strategies []string ) (bool , string , error ) {
1566
+ // retainKeys strategies and a string for the other strategy.
1567
+ func extractRetainKeysPatchStrategy (strategies []string ) (bool , string , error ) {
1546
1568
switch len (strategies ) {
1547
1569
case 0 :
1548
1570
return false , "" , nil
1549
1571
case 1 :
1550
1572
singleStrategy := strategies [0 ]
1551
1573
switch singleStrategy {
1552
- case replaceKeysStrategy :
1574
+ case retainKeysStrategy :
1553
1575
return true , "" , nil
1554
1576
default :
1555
1577
return false , singleStrategy , nil
1556
1578
}
1557
1579
case 2 :
1558
1580
switch {
1559
- case strategies [0 ] == replaceKeysStrategy :
1581
+ case strategies [0 ] == retainKeysStrategy :
1560
1582
return true , strategies [1 ], nil
1561
- case strategies [1 ] == replaceKeysStrategy :
1583
+ case strategies [1 ] == retainKeysStrategy :
1562
1584
return true , strategies [0 ], nil
1563
1585
default :
1564
1586
return false , "" , fmt .Errorf ("unexpected patch strategy: %v" , strategies )
0 commit comments