Skip to content

Commit cb66659

Browse files
author
ymqytw
committed
address some comments
1 parent 3e15bc5 commit cb66659

File tree

4 files changed

+437
-416
lines changed

4 files changed

+437
-416
lines changed

staging/src/k8s.io/apimachinery/pkg/util/mergepatch/errors.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import (
2323
)
2424

2525
var (
26-
ErrBadJSONDoc = errors.New("invalid JSON document")
27-
ErrNoListOfLists = errors.New("lists of lists are not supported")
28-
ErrBadPatchFormatForPrimitiveList = errors.New("invalid patch format of primitive list")
29-
ErrBadPatchFormatForReplaceKeys = errors.New("invalid patch format of replaceKeys")
30-
ErrPatchContentNotMatchReplaceKeys = errors.New("patch content doesn't match replaceKeys list")
26+
ErrBadJSONDoc = errors.New("invalid JSON document")
27+
ErrNoListOfLists = errors.New("lists of lists are not supported")
28+
ErrBadPatchFormatForPrimitiveList = errors.New("invalid patch format of primitive list")
29+
ErrBadPatchFormatForRetainKeys = errors.New("invalid patch format of retainKeys")
30+
ErrPatchContentNotMatchRetainKeys = errors.New("patch content doesn't match retainKeys list")
3131
)
3232

3333
func ErrNoMergeKey(m map[string]interface{}, k string) error {

staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/BUILD

-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ go_library(
2929
deps = [
3030
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
3131
"//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
32-
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
3332
"//vendor/k8s.io/apimachinery/third_party/forked/golang/json:go_default_library",
3433
],
3534
)

staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go

+74-52
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424

2525
"k8s.io/apimachinery/pkg/util/json"
2626
"k8s.io/apimachinery/pkg/util/mergepatch"
27-
"k8s.io/apimachinery/pkg/util/sets"
2827
forkedjson "k8s.io/apimachinery/third_party/forked/golang/json"
2928
)
3029

@@ -44,10 +43,10 @@ const (
4443
replaceDirective = "replace"
4544
mergeDirective = "merge"
4645

47-
replaceKeysStrategy = "replaceKeys"
46+
retainKeysStrategy = "retainKeys"
4847

4948
deleteFromPrimitiveListDirectivePrefix = "$deleteFromPrimitiveList"
50-
replaceKeysDirective = "$" + replaceKeysStrategy
49+
retainKeysDirective = "$" + retainKeysStrategy
5150
)
5251

5352
// JSONMap is a representations of JSON object encoded as map[string]interface{}
@@ -62,16 +61,20 @@ type DiffOptions struct {
6261
IgnoreChangesAndAdditions bool
6362
// IgnoreDeletions indicates if we keep the deletions in the patch.
6463
IgnoreDeletions bool
65-
// We introduce a new value replaceKeys for patchStrategy.
64+
// We introduce a new value retainKeys for patchStrategy.
6665
// It indicates that all fields needing to be preserved must be
67-
// present in the `replaceKeys` list.
66+
// present in the `retainKeys` list.
6867
// And the fields that are present will be merged with live object.
6968
// All the missing fields will be cleared when patching.
70-
ReplaceKeys bool
69+
BuildRetainKeysDirective bool
7170
}
7271

7372
type MergeOptions struct {
7473
// 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
7578
MergeParallelList bool
7679
// IgnoreUnmatchedNulls indicates if we should process the unmatched nulls.
7780
IgnoreUnmatchedNulls bool
@@ -134,18 +137,29 @@ func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{
134137
}
135138

136139
// 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
137149
func diffMaps(original, modified map[string]interface{}, t reflect.Type, diffOptions DiffOptions) (map[string]interface{}, error) {
138150
patch := map[string]interface{}{}
151+
// Get the underlying type for pointers
139152
if t.Kind() == reflect.Ptr {
140153
t = t.Elem()
141154
}
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))
144157

158+
// Compare each value in the modified map against the value in the original map
145159
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)
149163
}
150164

151165
originalValue, ok := original[key]
@@ -158,6 +172,7 @@ func diffMaps(original, modified map[string]interface{}, t reflect.Type, diffOpt
158172
}
159173

160174
// 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?
161176
foundDirectiveMarker, err := handleDirectiveMarker(key, originalValue, modifiedValue, patch)
162177
if err != nil {
163178
return nil, err
@@ -191,12 +206,13 @@ func diffMaps(original, modified map[string]interface{}, t reflect.Type, diffOpt
191206
}
192207

193208
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
196212
// - there are additional field in original that need to be cleared
197-
if len(replaceKeysList) > 0 &&
213+
if len(retainKeysList) > 0 &&
198214
(len(patch) > 0 || hasAdditionalNewField(original, modified)) {
199-
patch[replaceKeysDirective] = sortScalars(replaceKeysList)
215+
patch[retainKeysDirective] = sortScalars(retainKeysList)
200216
}
201217
return patch, nil
202218
}
@@ -238,11 +254,11 @@ func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]in
238254
// Otherwise, return the error
239255
return err
240256
}
241-
replaceKeys, patchStrategy, err := extractReplaceKeysPatchStrategy(fieldPatchStrategies)
257+
retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies)
242258
if err != nil {
243259
return err
244260
}
245-
diffOptions.ReplaceKeys = replaceKeys
261+
diffOptions.BuildRetainKeysDirective = retainKeys
246262
switch patchStrategy {
247263
// The patch strategic from metadata tells us to replace the entire object instead of diffing it
248264
case replaceDirective:
@@ -280,14 +296,14 @@ func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, pat
280296
// Otherwise, return the error
281297
return err
282298
}
283-
replaceKeys, patchStrategy, err := extractReplaceKeysPatchStrategy(fieldPatchStrategies)
299+
retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies)
284300
if err != nil {
285301
return err
286302
}
287303
switch patchStrategy {
288304
// Merge the 2 slices using mergePatchKey
289305
case mergeDirective:
290-
diffOptions.ReplaceKeys = replaceKeys
306+
diffOptions.BuildRetainKeysDirective = retainKeys
291307
addList, deletionList, err := diffLists(originalValue, modifiedValue, fieldType.Elem(), fieldPatchMergeKey, diffOptions)
292308
if err != nil {
293309
return err
@@ -601,6 +617,7 @@ func getTagStructType(dataStruct interface{}) (reflect.Type, error) {
601617
}
602618

603619
t := reflect.TypeOf(dataStruct)
620+
// Get the underlying type for pointers
604621
if t.Kind() == reflect.Ptr {
605622
t = t.Elem()
606623
}
@@ -652,47 +669,53 @@ func preprocessDeletionListForMerging(key string, original map[string]interface{
652669
return false, false, "", nil
653670
}
654671

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]
660679
if !foundInPatch {
661680
return nil
662681
}
663-
delete(patch, replaceKeysDirective)
682+
// cleanup the directive
683+
delete(patch, retainKeysDirective)
664684

665685
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]
667689
if foundInOriginal {
668-
if !reflect.DeepEqual(replaceKeysInOriginal, replaceKeysInPatch) {
690+
if !reflect.DeepEqual(retainKeysInOriginal, retainKeysInPatch) {
669691
// 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)
671693
}
672694
} else {
673-
original[replaceKeysDirective] = replaceKeysInPatch
695+
original[retainKeysDirective] = retainKeysInPatch
674696
}
675697
return nil
676698
}
677699

678-
replaceKeysList, ok := replaceKeysInPatch.([]interface{})
700+
retainKeysList, ok := retainKeysInPatch.([]interface{})
679701
if !ok {
680-
return mergepatch.ErrBadPatchFormatForReplaceKeys
702+
return mergepatch.ErrBadPatchFormatForRetainKeys
681703
}
682704

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{}{}
687710
}
688711
for k, v := range patch {
689712
if v == nil || strings.HasPrefix(k, deleteFromPrimitiveListDirectivePrefix) {
690713
continue
691714
}
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,
693716
// the patch is invalid.
694717
if _, found := m[k]; !found {
695-
return mergepatch.ErrPatchContentNotMatchReplaceKeys
718+
return mergepatch.ErrBadPatchFormatForRetainKeys
696719
}
697720
}
698721

@@ -723,7 +746,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio
723746
original = map[string]interface{}{}
724747
}
725748

726-
err := processReplaceKeys(original, patch, mergeOptions)
749+
err := applyRetainKeysDirective(original, patch, mergeOptions)
727750
if err != nil {
728751
return nil, err
729752
}
@@ -778,7 +801,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio
778801
if err != nil {
779802
return nil, err
780803
}
781-
_, patchStrategy, err := extractReplaceKeysPatchStrategy(fieldPatchStrategies)
804+
_, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies)
782805
if err != nil {
783806
return nil, err
784807
}
@@ -1028,10 +1051,10 @@ func sortMergeListsByName(mapJSON []byte, dataStruct interface{}) ([]byte, error
10281051
func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[string]interface{}, error) {
10291052
newS := map[string]interface{}{}
10301053
for k, v := range s {
1031-
if k == replaceKeysDirective {
1054+
if k == retainKeysDirective {
10321055
typedV, ok := v.([]interface{})
10331056
if !ok {
1034-
return nil, mergepatch.ErrBadPatchFormatForReplaceKeys
1057+
return nil, mergepatch.ErrBadPatchFormatForRetainKeys
10351058
}
10361059
v = sortScalars(typedV)
10371060
} else if strings.HasPrefix(k, deleteFromPrimitiveListDirectivePrefix) {
@@ -1040,13 +1063,12 @@ func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[stri
10401063
return nil, mergepatch.ErrBadPatchFormatForPrimitiveList
10411064
}
10421065
v = uniqifyAndSortScalars(typedV)
1043-
} else if k != directiveMarker && k != replaceKeysDirective &&
1044-
!strings.HasPrefix(k, deleteFromPrimitiveListDirectivePrefix) {
1066+
} else if k != directiveMarker {
10451067
fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, k)
10461068
if err != nil {
10471069
return nil, err
10481070
}
1049-
_, patchStrategy, err := extractReplaceKeysPatchStrategy(fieldPatchStrategies)
1071+
_, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies)
10501072
if err != nil {
10511073
return nil, err
10521074
}
@@ -1305,13 +1327,13 @@ func mergingMapFieldsHaveConflicts(
13051327

13061328
func mapsHaveConflicts(typedLeft, typedRight map[string]interface{}, structType reflect.Type) (bool, error) {
13071329
for key, leftValue := range typedLeft {
1308-
if key != directiveMarker && key != replaceKeysDirective {
1330+
if key != directiveMarker && key != retainKeysDirective {
13091331
if rightValue, ok := typedRight[key]; ok {
13101332
fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(structType, key)
13111333
if err != nil {
13121334
return true, err
13131335
}
1314-
_, patchStrategy, err := extractReplaceKeysPatchStrategy(fieldPatchStrategies)
1336+
_, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies)
13151337
if err != nil {
13161338
return true, err
13171339
}
@@ -1539,26 +1561,26 @@ func sliceTypeAssertion(original, patch interface{}) ([]interface{}, []interface
15391561
return typedOriginal, typedPatch, nil
15401562
}
15411563

1542-
// extractReplaceKeysPatchStrategy process patch strategy, which is a string may contains multiple
1564+
// extractRetainKeysPatchStrategy process patch strategy, which is a string may contains multiple
15431565
// 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) {
15461568
switch len(strategies) {
15471569
case 0:
15481570
return false, "", nil
15491571
case 1:
15501572
singleStrategy := strategies[0]
15511573
switch singleStrategy {
1552-
case replaceKeysStrategy:
1574+
case retainKeysStrategy:
15531575
return true, "", nil
15541576
default:
15551577
return false, singleStrategy, nil
15561578
}
15571579
case 2:
15581580
switch {
1559-
case strategies[0] == replaceKeysStrategy:
1581+
case strategies[0] == retainKeysStrategy:
15601582
return true, strategies[1], nil
1561-
case strategies[1] == replaceKeysStrategy:
1583+
case strategies[1] == retainKeysStrategy:
15621584
return true, strategies[0], nil
15631585
default:
15641586
return false, "", fmt.Errorf("unexpected patch strategy: %v", strategies)

0 commit comments

Comments
 (0)