2 Copyright 2014 The Kubernetes Authors.
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
17 package strategicpatch
25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26 "k8s.io/apimachinery/pkg/util/json"
27 "k8s.io/apimachinery/pkg/util/mergepatch"
30 // An alternate implementation of JSON Merge Patch
31 // (https://tools.ietf.org/html/rfc7386) which supports the ability to annotate
32 // certain fields with metadata that indicates whether the elements of JSON
33 // lists should be merged or replaced.
35 // For more information, see the PATCH section of docs/devel/api-conventions.md.
37 // Some of the content of this package was borrowed with minor adaptations from
38 // evanphx/json-patch and openshift/origin.
41 directiveMarker = "$patch"
42 deleteDirective = "delete"
43 replaceDirective = "replace"
44 mergeDirective = "merge"
46 retainKeysStrategy = "retainKeys"
48 deleteFromPrimitiveListDirectivePrefix = "$deleteFromPrimitiveList"
49 retainKeysDirective = "$" + retainKeysStrategy
50 setElementOrderDirectivePrefix = "$setElementOrder"
53 // JSONMap is a representations of JSON object encoded as map[string]interface{}
54 // where the children can be either map[string]interface{}, []interface{} or
56 // Operating on JSONMap representation is much faster as it doesn't require any
57 // json marshaling and/or unmarshaling operations.
58 type JSONMap map[string]interface{}
60 type DiffOptions struct {
61 // SetElementOrder determines whether we generate the $setElementOrder parallel list.
63 // IgnoreChangesAndAdditions indicates if we keep the changes and additions in the patch.
64 IgnoreChangesAndAdditions bool
65 // IgnoreDeletions indicates if we keep the deletions in the patch.
67 // We introduce a new value retainKeys for patchStrategy.
68 // It indicates that all fields needing to be preserved must be
69 // present in the `retainKeys` list.
70 // And the fields that are present will be merged with live object.
71 // All the missing fields will be cleared when patching.
72 BuildRetainKeysDirective bool
75 type MergeOptions struct {
76 // MergeParallelList indicates if we are merging the parallel list.
77 // We don't merge parallel list when calling mergeMap() in CreateThreeWayMergePatch()
78 // which is called client-side.
79 // We merge parallel list iff when calling mergeMap() in StrategicMergeMapPatch()
80 // which is called server-side
81 MergeParallelList bool
82 // IgnoreUnmatchedNulls indicates if we should process the unmatched nulls.
83 IgnoreUnmatchedNulls bool
86 // The following code is adapted from github.com/openshift/origin/pkg/util/jsonmerge.
87 // Instead of defining a Delta that holds an original, a patch and a set of preconditions,
88 // the reconcile method accepts a set of preconditions as an argument.
90 // CreateTwoWayMergePatch creates a patch that can be passed to StrategicMergePatch from an original
91 // document and a modified document, which are passed to the method as json encoded content. It will
92 // return a patch that yields the modified document when applied to the original document, or an error
93 // if either of the two documents is invalid.
94 func CreateTwoWayMergePatch(original, modified []byte, dataStruct interface{}, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
95 schema, err := NewPatchMetaFromStruct(dataStruct)
100 return CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema, fns...)
103 func CreateTwoWayMergePatchUsingLookupPatchMeta(
104 original, modified []byte, schema LookupPatchMeta, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
105 originalMap := map[string]interface{}{}
106 if len(original) > 0 {
107 if err := json.Unmarshal(original, &originalMap); err != nil {
108 return nil, mergepatch.ErrBadJSONDoc
112 modifiedMap := map[string]interface{}{}
113 if len(modified) > 0 {
114 if err := json.Unmarshal(modified, &modifiedMap); err != nil {
115 return nil, mergepatch.ErrBadJSONDoc
119 patchMap, err := CreateTwoWayMergeMapPatchUsingLookupPatchMeta(originalMap, modifiedMap, schema, fns...)
124 return json.Marshal(patchMap)
127 // CreateTwoWayMergeMapPatch creates a patch from an original and modified JSON objects,
129 // The serialized version of the map can then be passed to StrategicMergeMapPatch.
130 func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{}, fns ...mergepatch.PreconditionFunc) (JSONMap, error) {
131 schema, err := NewPatchMetaFromStruct(dataStruct)
136 return CreateTwoWayMergeMapPatchUsingLookupPatchMeta(original, modified, schema, fns...)
139 func CreateTwoWayMergeMapPatchUsingLookupPatchMeta(original, modified JSONMap, schema LookupPatchMeta, fns ...mergepatch.PreconditionFunc) (JSONMap, error) {
140 diffOptions := DiffOptions{
141 SetElementOrder: true,
143 patchMap, err := diffMaps(original, modified, schema, diffOptions)
148 // Apply the preconditions to the patch, and return an error if any of them fail.
149 for _, fn := range fns {
151 return nil, mergepatch.NewErrPreconditionFailed(patchMap)
158 // Returns a (recursive) strategic merge patch that yields modified when applied to original.
160 // - Adding fields to the patch present in modified, missing from original
161 // - Setting fields to the patch present in modified and original with different values
162 // - Delete fields present in original, missing from modified through
163 // - IFF map field - set to nil in patch
164 // - IFF list of maps && merge strategy - use deleteDirective for the elements
165 // - IFF list of primitives && merge strategy - use parallel deletion list
166 // - IFF list of maps or primitives with replace strategy (default) - set patch value to the value in modified
167 // - Build $retainKeys directive for fields with retainKeys patch strategy
168 func diffMaps(original, modified map[string]interface{}, schema LookupPatchMeta, diffOptions DiffOptions) (map[string]interface{}, error) {
169 patch := map[string]interface{}{}
171 // This will be used to build the $retainKeys directive sent in the patch
172 retainKeysList := make([]interface{}, 0, len(modified))
174 // Compare each value in the modified map against the value in the original map
175 for key, modifiedValue := range modified {
176 // Get the underlying type for pointers
177 if diffOptions.BuildRetainKeysDirective && modifiedValue != nil {
178 retainKeysList = append(retainKeysList, key)
181 originalValue, ok := original[key]
183 // Key was added, so add to patch
184 if !diffOptions.IgnoreChangesAndAdditions {
185 patch[key] = modifiedValue
190 // The patch may have a patch directive
191 // TODO: figure out if we need this. This shouldn't be needed by apply. When would the original map have patch directives in it?
192 foundDirectiveMarker, err := handleDirectiveMarker(key, originalValue, modifiedValue, patch)
196 if foundDirectiveMarker {
200 if reflect.TypeOf(originalValue) != reflect.TypeOf(modifiedValue) {
201 // Types have changed, so add to patch
202 if !diffOptions.IgnoreChangesAndAdditions {
203 patch[key] = modifiedValue
208 // Types are the same, so compare values
209 switch originalValueTyped := originalValue.(type) {
210 case map[string]interface{}:
211 modifiedValueTyped := modifiedValue.(map[string]interface{})
212 err = handleMapDiff(key, originalValueTyped, modifiedValueTyped, patch, schema, diffOptions)
214 modifiedValueTyped := modifiedValue.([]interface{})
215 err = handleSliceDiff(key, originalValueTyped, modifiedValueTyped, patch, schema, diffOptions)
217 replacePatchFieldIfNotEqual(key, originalValue, modifiedValue, patch, diffOptions)
224 updatePatchIfMissing(original, modified, patch, diffOptions)
225 // Insert the retainKeysList iff there are values present in the retainKeysList and
226 // either of the following is true:
227 // - the patch is not empty
228 // - there are additional field in original that need to be cleared
229 if len(retainKeysList) > 0 &&
230 (len(patch) > 0 || hasAdditionalNewField(original, modified)) {
231 patch[retainKeysDirective] = sortScalars(retainKeysList)
236 // handleDirectiveMarker handles how to diff directive marker between 2 objects
237 func handleDirectiveMarker(key string, originalValue, modifiedValue interface{}, patch map[string]interface{}) (bool, error) {
238 if key == directiveMarker {
239 originalString, ok := originalValue.(string)
241 return false, fmt.Errorf("invalid value for special key: %s", directiveMarker)
243 modifiedString, ok := modifiedValue.(string)
245 return false, fmt.Errorf("invalid value for special key: %s", directiveMarker)
247 if modifiedString != originalString {
248 patch[directiveMarker] = modifiedValue
255 // handleMapDiff diff between 2 maps `originalValueTyped` and `modifiedValue`,
256 // puts the diff in the `patch` associated with `key`
257 // key is the key associated with originalValue and modifiedValue.
258 // originalValue, modifiedValue are the old and new value respectively.They are both maps
259 // patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue
260 // diffOptions contains multiple options to control how we do the diff.
261 func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]interface{},
262 schema LookupPatchMeta, diffOptions DiffOptions) error {
263 subschema, patchMeta, err := schema.LookupPatchMetadataForStruct(key)
266 // We couldn't look up metadata for the field
267 // If the values are identical, this doesn't matter, no patch is needed
268 if reflect.DeepEqual(originalValue, modifiedValue) {
271 // Otherwise, return the error
274 retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies())
278 diffOptions.BuildRetainKeysDirective = retainKeys
279 switch patchStrategy {
280 // The patch strategic from metadata tells us to replace the entire object instead of diffing it
281 case replaceDirective:
282 if !diffOptions.IgnoreChangesAndAdditions {
283 patch[key] = modifiedValue
286 patchValue, err := diffMaps(originalValue, modifiedValue, subschema, diffOptions)
290 // Maps were not identical, use provided patch value
291 if len(patchValue) > 0 {
292 patch[key] = patchValue
298 // handleSliceDiff diff between 2 slices `originalValueTyped` and `modifiedValue`,
299 // puts the diff in the `patch` associated with `key`
300 // key is the key associated with originalValue and modifiedValue.
301 // originalValue, modifiedValue are the old and new value respectively.They are both slices
302 // patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue
303 // diffOptions contains multiple options to control how we do the diff.
304 func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, patch map[string]interface{},
305 schema LookupPatchMeta, diffOptions DiffOptions) error {
306 subschema, patchMeta, err := schema.LookupPatchMetadataForSlice(key)
308 // We couldn't look up metadata for the field
309 // If the values are identical, this doesn't matter, no patch is needed
310 if reflect.DeepEqual(originalValue, modifiedValue) {
313 // Otherwise, return the error
316 retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies())
320 switch patchStrategy {
321 // Merge the 2 slices using mergePatchKey
323 diffOptions.BuildRetainKeysDirective = retainKeys
324 addList, deletionList, setOrderList, err := diffLists(originalValue, modifiedValue, subschema, patchMeta.GetPatchMergeKey(), diffOptions)
328 if len(addList) > 0 {
331 // generate a parallel list for deletion
332 if len(deletionList) > 0 {
333 parallelDeletionListKey := fmt.Sprintf("%s/%s", deleteFromPrimitiveListDirectivePrefix, key)
334 patch[parallelDeletionListKey] = deletionList
336 if len(setOrderList) > 0 {
337 parallelSetOrderListKey := fmt.Sprintf("%s/%s", setElementOrderDirectivePrefix, key)
338 patch[parallelSetOrderListKey] = setOrderList
341 replacePatchFieldIfNotEqual(key, originalValue, modifiedValue, patch, diffOptions)
346 // replacePatchFieldIfNotEqual updates the patch if original and modified are not deep equal
347 // if diffOptions.IgnoreChangesAndAdditions is false.
348 // original is the old value, maybe either the live cluster object or the last applied configuration
349 // modified is the new value, is always the users new config
350 func replacePatchFieldIfNotEqual(key string, original, modified interface{},
351 patch map[string]interface{}, diffOptions DiffOptions) {
352 if diffOptions.IgnoreChangesAndAdditions {
353 // Ignoring changes - do nothing
356 if reflect.DeepEqual(original, modified) {
357 // Contents are identical - do nothing
360 // Create a patch to replace the old value with the new one
361 patch[key] = modified
364 // updatePatchIfMissing iterates over `original` when ignoreDeletions is false.
365 // Clear the field whose key is not present in `modified`.
366 // original is the old value, maybe either the live cluster object or the last applied configuration
367 // modified is the new value, is always the users new config
368 func updatePatchIfMissing(original, modified, patch map[string]interface{}, diffOptions DiffOptions) {
369 if diffOptions.IgnoreDeletions {
370 // Ignoring deletion - do nothing
373 // Add nils for deleted values
374 for key := range original {
375 if _, found := modified[key]; !found {
381 // validateMergeKeyInLists checks if each map in the list has the mentryerge key.
382 func validateMergeKeyInLists(mergeKey string, lists ...[]interface{}) error {
383 for _, list := range lists {
384 for _, item := range list {
385 m, ok := item.(map[string]interface{})
387 return mergepatch.ErrBadArgType(m, item)
389 if _, ok = m[mergeKey]; !ok {
390 return mergepatch.ErrNoMergeKey(m, mergeKey)
397 // normalizeElementOrder sort `patch` list by `patchOrder` and sort `serverOnly` list by `serverOrder`.
398 // Then it merges the 2 sorted lists.
399 // It guarantee the relative order in the patch list and in the serverOnly list is kept.
400 // `patch` is a list of items in the patch, and `serverOnly` is a list of items in the live object.
401 // `patchOrder` is the order we want `patch` list to have and
402 // `serverOrder` is the order we want `serverOnly` list to have.
403 // kind is the kind of each item in the lists `patch` and `serverOnly`.
404 func normalizeElementOrder(patch, serverOnly, patchOrder, serverOrder []interface{}, mergeKey string, kind reflect.Kind) ([]interface{}, error) {
405 patch, err := normalizeSliceOrder(patch, patchOrder, mergeKey, kind)
409 serverOnly, err = normalizeSliceOrder(serverOnly, serverOrder, mergeKey, kind)
413 all := mergeSortedSlice(serverOnly, patch, serverOrder, mergeKey, kind)
418 // mergeSortedSlice merges the 2 sorted lists by serverOrder with best effort.
419 // It will insert each item in `left` list to `right` list. In most cases, the 2 lists will be interleaved.
420 // The relative order of left and right are guaranteed to be kept.
421 // They have higher precedence than the order in the live list.
422 // The place for a item in `left` is found by:
423 // scan from the place of last insertion in `right` to the end of `right`,
424 // the place is before the first item that is greater than the item we want to insert.
425 // example usage: using server-only items as left and patch items as right. We insert server-only items
426 // to patch list. We use the order of live object as record for comparison.
427 func mergeSortedSlice(left, right, serverOrder []interface{}, mergeKey string, kind reflect.Kind) []interface{} {
428 // Returns if l is less than r, and if both have been found.
429 // If l and r both present and l is in front of r, l is less than r.
430 less := func(l, r interface{}) (bool, bool) {
431 li := index(serverOrder, l, mergeKey, kind)
432 ri := index(serverOrder, r, mergeKey, kind)
433 if li >= 0 && ri >= 0 {
440 // left and right should be non-overlapping.
441 size := len(left) + len(right)
443 s := make([]interface{}, size, size)
445 for k := 0; k < size; k++ {
446 if i >= len(left) && j < len(right) {
447 // have items left in `right` list
450 } else if j >= len(right) && i < len(left) {
451 // have items left in `left` list
455 // compare them if i and j are both in bound
456 less, foundBoth := less(left[i], right[j])
457 if foundBoth && less {
469 // index returns the index of the item in the given items, or -1 if it doesn't exist
470 // l must NOT be a slice of slices, this should be checked before calling.
471 func index(l []interface{}, valToLookUp interface{}, mergeKey string, kind reflect.Kind) int {
472 var getValFn func(interface{}) interface{}
473 // Get the correct `getValFn` based on item `kind`.
474 // It should return the value of merge key for maps and
475 // return the item for other kinds.
478 getValFn = func(item interface{}) interface{} {
479 typedItem, ok := item.(map[string]interface{})
483 val := typedItem[mergeKey]
487 getValFn = func(item interface{}) interface{} {
492 for i, v := range l {
493 if getValFn(valToLookUp) == getValFn(v) {
500 // extractToDeleteItems takes a list and
501 // returns 2 lists: one contains items that should be kept and the other contains items to be deleted.
502 func extractToDeleteItems(l []interface{}) ([]interface{}, []interface{}, error) {
503 var nonDelete, toDelete []interface{}
504 for _, v := range l {
505 m, ok := v.(map[string]interface{})
507 return nil, nil, mergepatch.ErrBadArgType(m, v)
510 directive, foundDirective := m[directiveMarker]
511 if foundDirective && directive == deleteDirective {
512 toDelete = append(toDelete, v)
514 nonDelete = append(nonDelete, v)
517 return nonDelete, toDelete, nil
520 // normalizeSliceOrder sort `toSort` list by `order`
521 func normalizeSliceOrder(toSort, order []interface{}, mergeKey string, kind reflect.Kind) ([]interface{}, error) {
522 var toDelete []interface{}
523 if kind == reflect.Map {
524 // make sure each item in toSort, order has merge key
525 err := validateMergeKeyInLists(mergeKey, toSort, order)
529 toSort, toDelete, err = extractToDeleteItems(toSort)
535 sort.SliceStable(toSort, func(i, j int) bool {
536 if ii := index(order, toSort[i], mergeKey, kind); ii >= 0 {
537 if ij := index(order, toSort[j], mergeKey, kind); ij >= 0 {
543 toSort = append(toSort, toDelete...)
547 // Returns a (recursive) strategic merge patch, a parallel deletion list if necessary and
548 // another list to set the order of the list
549 // Only list of primitives with merge strategy will generate a parallel deletion list.
550 // These two lists should yield modified when applied to original, for lists with merge semantics.
551 func diffLists(original, modified []interface{}, schema LookupPatchMeta, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, []interface{}, error) {
552 if len(original) == 0 {
553 // Both slices are empty - do nothing
554 if len(modified) == 0 || diffOptions.IgnoreChangesAndAdditions {
555 return nil, nil, nil, nil
558 // Old slice was empty - add all elements from the new slice
559 return modified, nil, nil, nil
562 elementType, err := sliceElementType(original, modified)
564 return nil, nil, nil, err
567 var patchList, deleteList, setOrderList []interface{}
568 kind := elementType.Kind()
571 patchList, deleteList, err = diffListsOfMaps(original, modified, schema, mergeKey, diffOptions)
573 return nil, nil, nil, err
575 patchList, err = normalizeSliceOrder(patchList, modified, mergeKey, kind)
577 return nil, nil, nil, err
579 orderSame, err := isOrderSame(original, modified, mergeKey)
581 return nil, nil, nil, err
583 // append the deletions to the end of the patch list.
584 patchList = append(patchList, deleteList...)
586 // generate the setElementOrder list when there are content changes or order changes
587 if diffOptions.SetElementOrder &&
588 ((!diffOptions.IgnoreChangesAndAdditions && (len(patchList) > 0 || !orderSame)) ||
589 (!diffOptions.IgnoreDeletions && len(patchList) > 0)) {
590 // Generate a list of maps that each item contains only the merge key.
591 setOrderList = make([]interface{}, len(modified))
592 for i, v := range modified {
593 typedV := v.(map[string]interface{})
594 setOrderList[i] = map[string]interface{}{
595 mergeKey: typedV[mergeKey],
600 // Lists of Lists are not permitted by the api
601 return nil, nil, nil, mergepatch.ErrNoListOfLists
603 patchList, deleteList, err = diffListsOfScalars(original, modified, diffOptions)
605 return nil, nil, nil, err
607 patchList, err = normalizeSliceOrder(patchList, modified, mergeKey, kind)
608 // generate the setElementOrder list when there are content changes or order changes
609 if diffOptions.SetElementOrder && ((!diffOptions.IgnoreDeletions && len(deleteList) > 0) ||
610 (!diffOptions.IgnoreChangesAndAdditions && !reflect.DeepEqual(original, modified))) {
611 setOrderList = modified
614 return patchList, deleteList, setOrderList, err
617 // isOrderSame checks if the order in a list has changed
618 func isOrderSame(original, modified []interface{}, mergeKey string) (bool, error) {
619 if len(original) != len(modified) {
622 for i, modifiedItem := range modified {
623 equal, err := mergeKeyValueEqual(original[i], modifiedItem, mergeKey)
624 if err != nil || !equal {
631 // diffListsOfScalars returns 2 lists, the first one is addList and the second one is deletionList.
632 // Argument diffOptions.IgnoreChangesAndAdditions controls if calculate addList. true means not calculate.
633 // Argument diffOptions.IgnoreDeletions controls if calculate deletionList. true means not calculate.
634 // original may be changed, but modified is guaranteed to not be changed
635 func diffListsOfScalars(original, modified []interface{}, diffOptions DiffOptions) ([]interface{}, []interface{}, error) {
636 modifiedCopy := make([]interface{}, len(modified))
637 copy(modifiedCopy, modified)
638 // Sort the scalars for easier calculating the diff
639 originalScalars := sortScalars(original)
640 modifiedScalars := sortScalars(modifiedCopy)
642 originalIndex, modifiedIndex := 0, 0
643 addList := []interface{}{}
644 deletionList := []interface{}{}
647 originalInBounds := originalIndex < len(originalScalars)
648 modifiedInBounds := modifiedIndex < len(modifiedScalars)
649 if !originalInBounds && !modifiedInBounds {
652 // we need to compare the string representation of the scalar,
653 // because the scalar is an interface which doesn't support either < or >
654 // And that's how func sortScalars compare scalars.
655 var originalString, modifiedString string
656 var originalValue, modifiedValue interface{}
657 if originalInBounds {
658 originalValue = originalScalars[originalIndex]
659 originalString = fmt.Sprintf("%v", originalValue)
661 if modifiedInBounds {
662 modifiedValue = modifiedScalars[modifiedIndex]
663 modifiedString = fmt.Sprintf("%v", modifiedValue)
666 originalV, modifiedV := compareListValuesAtIndex(originalInBounds, modifiedInBounds, originalString, modifiedString)
668 case originalV == nil && modifiedV == nil:
671 case originalV != nil && modifiedV == nil:
672 if !diffOptions.IgnoreDeletions {
673 deletionList = append(deletionList, originalValue)
676 case originalV == nil && modifiedV != nil:
677 if !diffOptions.IgnoreChangesAndAdditions {
678 addList = append(addList, modifiedValue)
682 return nil, nil, fmt.Errorf("Unexpected returned value from compareListValuesAtIndex: %v and %v", originalV, modifiedV)
686 return addList, deduplicateScalars(deletionList), nil
689 // If first return value is non-nil, list1 contains an element not present in list2
690 // If second return value is non-nil, list2 contains an element not present in list1
691 func compareListValuesAtIndex(list1Inbounds, list2Inbounds bool, list1Value, list2Value string) (interface{}, interface{}) {
692 bothInBounds := list1Inbounds && list2Inbounds
694 // scalars are identical
695 case bothInBounds && list1Value == list2Value:
697 // only list2 is in bound
700 // list2 has additional scalar
701 case bothInBounds && list1Value > list2Value:
702 return nil, list2Value
703 // only original is in bound
706 // original has additional scalar
707 case bothInBounds && list1Value < list2Value:
708 return list1Value, nil
714 // diffListsOfMaps takes a pair of lists and
715 // returns a (recursive) strategic merge patch list contains additions and changes and
716 // a deletion list contains deletions
717 func diffListsOfMaps(original, modified []interface{}, schema LookupPatchMeta, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, error) {
718 patch := make([]interface{}, 0, len(modified))
719 deletionList := make([]interface{}, 0, len(original))
721 originalSorted, err := sortMergeListsByNameArray(original, schema, mergeKey, false)
725 modifiedSorted, err := sortMergeListsByNameArray(modified, schema, mergeKey, false)
730 originalIndex, modifiedIndex := 0, 0
732 originalInBounds := originalIndex < len(originalSorted)
733 modifiedInBounds := modifiedIndex < len(modifiedSorted)
734 bothInBounds := originalInBounds && modifiedInBounds
735 if !originalInBounds && !modifiedInBounds {
739 var originalElementMergeKeyValueString, modifiedElementMergeKeyValueString string
740 var originalElementMergeKeyValue, modifiedElementMergeKeyValue interface{}
741 var originalElement, modifiedElement map[string]interface{}
742 if originalInBounds {
743 originalElement, originalElementMergeKeyValue, err = getMapAndMergeKeyValueByIndex(originalIndex, mergeKey, originalSorted)
747 originalElementMergeKeyValueString = fmt.Sprintf("%v", originalElementMergeKeyValue)
749 if modifiedInBounds {
750 modifiedElement, modifiedElementMergeKeyValue, err = getMapAndMergeKeyValueByIndex(modifiedIndex, mergeKey, modifiedSorted)
754 modifiedElementMergeKeyValueString = fmt.Sprintf("%v", modifiedElementMergeKeyValue)
758 case bothInBounds && ItemMatchesOriginalAndModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString):
759 // Merge key values are equal, so recurse
760 patchValue, err := diffMaps(originalElement, modifiedElement, schema, diffOptions)
764 if len(patchValue) > 0 {
765 patchValue[mergeKey] = modifiedElementMergeKeyValue
766 patch = append(patch, patchValue)
770 // only modified is in bound
771 case !originalInBounds:
773 // modified has additional map
774 case bothInBounds && ItemAddedToModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString):
775 if !diffOptions.IgnoreChangesAndAdditions {
776 patch = append(patch, modifiedElement)
779 // only original is in bound
780 case !modifiedInBounds:
782 // original has additional map
783 case bothInBounds && ItemRemovedFromModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString):
784 if !diffOptions.IgnoreDeletions {
785 // Item was deleted, so add delete directive
786 deletionList = append(deletionList, CreateDeleteDirective(mergeKey, originalElementMergeKeyValue))
792 return patch, deletionList, nil
795 // getMapAndMergeKeyValueByIndex return a map in the list and its merge key value given the index of the map.
796 func getMapAndMergeKeyValueByIndex(index int, mergeKey string, listOfMaps []interface{}) (map[string]interface{}, interface{}, error) {
797 m, ok := listOfMaps[index].(map[string]interface{})
799 return nil, nil, mergepatch.ErrBadArgType(m, listOfMaps[index])
802 val, ok := m[mergeKey]
804 return nil, nil, mergepatch.ErrNoMergeKey(m, mergeKey)
809 // StrategicMergePatch applies a strategic merge patch. The patch and the original document
810 // must be json encoded content. A patch can be created from an original and a modified document
811 // by calling CreateStrategicMergePatch.
812 func StrategicMergePatch(original, patch []byte, dataStruct interface{}) ([]byte, error) {
813 schema, err := NewPatchMetaFromStruct(dataStruct)
818 return StrategicMergePatchUsingLookupPatchMeta(original, patch, schema)
821 func StrategicMergePatchUsingLookupPatchMeta(original, patch []byte, schema LookupPatchMeta) ([]byte, error) {
822 originalMap, err := handleUnmarshal(original)
826 patchMap, err := handleUnmarshal(patch)
831 result, err := StrategicMergeMapPatchUsingLookupPatchMeta(originalMap, patchMap, schema)
836 return json.Marshal(result)
839 func handleUnmarshal(j []byte) (map[string]interface{}, error) {
844 m := map[string]interface{}{}
845 err := json.Unmarshal(j, &m)
847 return nil, mergepatch.ErrBadJSONDoc
852 // StrategicMergeMapPatch applies a strategic merge patch. The original and patch documents
853 // must be JSONMap. A patch can be created from an original and modified document by
854 // calling CreateTwoWayMergeMapPatch.
855 // Warning: the original and patch JSONMap objects are mutated by this function and should not be reused.
856 func StrategicMergeMapPatch(original, patch JSONMap, dataStruct interface{}) (JSONMap, error) {
857 schema, err := NewPatchMetaFromStruct(dataStruct)
862 // We need the go struct tags `patchMergeKey` and `patchStrategy` for fields that support a strategic merge patch.
863 // For native resources, we can easily figure out these tags since we know the fields.
865 // Because custom resources are decoded as Unstructured and because we're missing the metadata about how to handle
866 // each field in a strategic merge patch, we can't find the go struct tags. Hence, we can't easily do a strategic merge
867 // for custom resources. So we should fail fast and return an error.
868 if _, ok := dataStruct.(*unstructured.Unstructured); ok {
869 return nil, mergepatch.ErrUnsupportedStrategicMergePatchFormat
872 return StrategicMergeMapPatchUsingLookupPatchMeta(original, patch, schema)
875 func StrategicMergeMapPatchUsingLookupPatchMeta(original, patch JSONMap, schema LookupPatchMeta) (JSONMap, error) {
876 mergeOptions := MergeOptions{
877 MergeParallelList: true,
878 IgnoreUnmatchedNulls: true,
880 return mergeMap(original, patch, schema, mergeOptions)
883 // MergeStrategicMergeMapPatchUsingLookupPatchMeta merges strategic merge
884 // patches retaining `null` fields and parallel lists. If 2 patches change the
885 // same fields and the latter one will override the former one. If you don't
886 // want that happen, you need to run func MergingMapsHaveConflicts before
887 // merging these patches. Applying the resulting merged merge patch to a JSONMap
888 // yields the same as merging each strategic merge patch to the JSONMap in
890 func MergeStrategicMergeMapPatchUsingLookupPatchMeta(schema LookupPatchMeta, patches ...JSONMap) (JSONMap, error) {
891 mergeOptions := MergeOptions{
892 MergeParallelList: false,
893 IgnoreUnmatchedNulls: false,
897 for _, patch := range patches {
898 merged, err = mergeMap(merged, patch, schema, mergeOptions)
906 // handleDirectiveInMergeMap handles the patch directive when merging 2 maps.
907 func handleDirectiveInMergeMap(directive interface{}, patch map[string]interface{}) (map[string]interface{}, error) {
908 if directive == replaceDirective {
909 // If the patch contains "$patch: replace", don't merge it, just use the
910 // patch directly. Later on, we can add a single level replace that only
911 // affects the map that the $patch is in.
912 delete(patch, directiveMarker)
916 if directive == deleteDirective {
917 // If the patch contains "$patch: delete", don't merge it, just return
919 return map[string]interface{}{}, nil
922 return nil, mergepatch.ErrBadPatchType(directive, patch)
925 func containsDirectiveMarker(item interface{}) bool {
926 m, ok := item.(map[string]interface{})
928 if _, foundDirectiveMarker := m[directiveMarker]; foundDirectiveMarker {
935 func mergeKeyValueEqual(left, right interface{}, mergeKey string) (bool, error) {
936 if len(mergeKey) == 0 {
937 return left == right, nil
939 typedLeft, ok := left.(map[string]interface{})
941 return false, mergepatch.ErrBadArgType(typedLeft, left)
943 typedRight, ok := right.(map[string]interface{})
945 return false, mergepatch.ErrBadArgType(typedRight, right)
947 mergeKeyLeft, ok := typedLeft[mergeKey]
949 return false, mergepatch.ErrNoMergeKey(typedLeft, mergeKey)
951 mergeKeyRight, ok := typedRight[mergeKey]
953 return false, mergepatch.ErrNoMergeKey(typedRight, mergeKey)
955 return mergeKeyLeft == mergeKeyRight, nil
958 // extractKey trims the prefix and return the original key
959 func extractKey(s, prefix string) (string, error) {
960 substrings := strings.SplitN(s, "/", 2)
961 if len(substrings) <= 1 || substrings[0] != prefix {
963 case deleteFromPrimitiveListDirectivePrefix:
964 return "", mergepatch.ErrBadPatchFormatForPrimitiveList
965 case setElementOrderDirectivePrefix:
966 return "", mergepatch.ErrBadPatchFormatForSetElementOrderList
968 return "", fmt.Errorf("fail to find unknown prefix %q in %s\n", prefix, s)
971 return substrings[1], nil
974 // validatePatchUsingSetOrderList verifies:
975 // the relative order of any two items in the setOrderList list matches that in the patch list.
976 // the items in the patch list must be a subset or the same as the $setElementOrder list (deletions are ignored).
977 func validatePatchWithSetOrderList(patchList, setOrderList interface{}, mergeKey string) error {
978 typedSetOrderList, ok := setOrderList.([]interface{})
980 return mergepatch.ErrBadPatchFormatForSetElementOrderList
982 typedPatchList, ok := patchList.([]interface{})
984 return mergepatch.ErrBadPatchFormatForSetElementOrderList
986 if len(typedSetOrderList) == 0 || len(typedPatchList) == 0 {
990 var nonDeleteList, toDeleteList []interface{}
992 if len(mergeKey) > 0 {
993 nonDeleteList, toDeleteList, err = extractToDeleteItems(typedPatchList)
998 nonDeleteList = typedPatchList
1001 patchIndex, setOrderIndex := 0, 0
1002 for patchIndex < len(nonDeleteList) && setOrderIndex < len(typedSetOrderList) {
1003 if containsDirectiveMarker(nonDeleteList[patchIndex]) {
1007 mergeKeyEqual, err := mergeKeyValueEqual(nonDeleteList[patchIndex], typedSetOrderList[setOrderIndex], mergeKey)
1016 // If patchIndex is inbound but setOrderIndex if out of bound mean there are items mismatching between the patch list and setElementOrder list.
1017 // the second check is is a sanity check, and should always be true if the first is true.
1018 if patchIndex < len(nonDeleteList) && setOrderIndex >= len(typedSetOrderList) {
1019 return fmt.Errorf("The order in patch list:\n%v\n doesn't match %s list:\n%v\n", typedPatchList, setElementOrderDirectivePrefix, setOrderList)
1021 typedPatchList = append(nonDeleteList, toDeleteList...)
1025 // preprocessDeletionListForMerging preprocesses the deletion list.
1026 // it returns shouldContinue, isDeletionList, noPrefixKey
1027 func preprocessDeletionListForMerging(key string, original map[string]interface{},
1028 patchVal interface{}, mergeDeletionList bool) (bool, bool, string, error) {
1029 // If found a parallel list for deletion and we are going to merge the list,
1030 // overwrite the key to the original key and set flag isDeleteList
1031 foundParallelListPrefix := strings.HasPrefix(key, deleteFromPrimitiveListDirectivePrefix)
1032 if foundParallelListPrefix {
1033 if !mergeDeletionList {
1034 original[key] = patchVal
1035 return true, false, "", nil
1037 originalKey, err := extractKey(key, deleteFromPrimitiveListDirectivePrefix)
1038 return false, true, originalKey, err
1040 return false, false, "", nil
1043 // applyRetainKeysDirective looks for a retainKeys directive and applies to original
1044 // - if no directive exists do nothing
1045 // - if directive is found, clear keys in original missing from the directive list
1046 // - validate that all keys present in the patch are present in the retainKeys directive
1047 // 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
1048 func applyRetainKeysDirective(original, patch map[string]interface{}, options MergeOptions) error {
1049 retainKeysInPatch, foundInPatch := patch[retainKeysDirective]
1053 // cleanup the directive
1054 delete(patch, retainKeysDirective)
1056 if !options.MergeParallelList {
1057 // If original is actually a patch, make sure the retainKeys directives are the same in both patches if present in both.
1058 // If not present in the original patch, copy from the modified patch.
1059 retainKeysInOriginal, foundInOriginal := original[retainKeysDirective]
1060 if foundInOriginal {
1061 if !reflect.DeepEqual(retainKeysInOriginal, retainKeysInPatch) {
1062 // This error actually should never happen.
1063 return fmt.Errorf("%v and %v are not deep equal: this may happen when calculating the 3-way diff patch", retainKeysInOriginal, retainKeysInPatch)
1066 original[retainKeysDirective] = retainKeysInPatch
1071 retainKeysList, ok := retainKeysInPatch.([]interface{})
1073 return mergepatch.ErrBadPatchFormatForRetainKeys
1076 // validate patch to make sure all fields in the patch are present in the retainKeysList.
1077 // The map is used only as a set, the value is never referenced
1078 m := map[interface{}]struct{}{}
1079 for _, v := range retainKeysList {
1082 for k, v := range patch {
1083 if v == nil || strings.HasPrefix(k, deleteFromPrimitiveListDirectivePrefix) ||
1084 strings.HasPrefix(k, setElementOrderDirectivePrefix) {
1087 // If there is an item present in the patch but not in the retainKeys list,
1088 // the patch is invalid.
1089 if _, found := m[k]; !found {
1090 return mergepatch.ErrBadPatchFormatForRetainKeys
1094 // clear not present fields
1095 for k := range original {
1096 if _, found := m[k]; !found {
1103 // mergePatchIntoOriginal processes $setElementOrder list.
1104 // When not merging the directive, it will make sure $setElementOrder list exist only in original.
1105 // When merging the directive, it will try to find the $setElementOrder list and
1106 // its corresponding patch list, validate it and merge it.
1107 // Then, sort them by the relative order in setElementOrder, patch list and live list.
1108 // The precedence is $setElementOrder > order in patch list > order in live list.
1109 // This function will delete the item after merging it to prevent process it again in the future.
1110 // Ref: https://git.k8s.io/community/contributors/design-proposals/cli/preserve-order-in-strategic-merge-patch.md
1111 func mergePatchIntoOriginal(original, patch map[string]interface{}, schema LookupPatchMeta, mergeOptions MergeOptions) error {
1112 for key, patchV := range patch {
1113 // Do nothing if there is no ordering directive
1114 if !strings.HasPrefix(key, setElementOrderDirectivePrefix) {
1118 setElementOrderInPatch := patchV
1119 // Copies directive from the second patch (`patch`) to the first patch (`original`)
1120 // and checks they are equal and delete the directive in the second patch
1121 if !mergeOptions.MergeParallelList {
1122 setElementOrderListInOriginal, ok := original[key]
1124 // check if the setElementOrder list in original and the one in patch matches
1125 if !reflect.DeepEqual(setElementOrderListInOriginal, setElementOrderInPatch) {
1126 return mergepatch.ErrBadPatchFormatForSetElementOrderList
1129 // move the setElementOrder list from patch to original
1130 original[key] = setElementOrderInPatch
1137 originalFieldValue, patchFieldValue, merged []interface{}
1138 patchStrategy string
1140 subschema LookupPatchMeta
1142 typedSetElementOrderList, ok := setElementOrderInPatch.([]interface{})
1144 return mergepatch.ErrBadArgType(typedSetElementOrderList, setElementOrderInPatch)
1146 // Trim the setElementOrderDirectivePrefix to get the key of the list field in original.
1147 originalKey, err := extractKey(key, setElementOrderDirectivePrefix)
1151 // try to find the list with `originalKey` in `original` and `modified` and merge them.
1152 originalList, foundOriginal := original[originalKey]
1153 patchList, foundPatch := patch[originalKey]
1155 originalFieldValue, ok = originalList.([]interface{})
1157 return mergepatch.ErrBadArgType(originalFieldValue, originalList)
1161 patchFieldValue, ok = patchList.([]interface{})
1163 return mergepatch.ErrBadArgType(patchFieldValue, patchList)
1166 subschema, patchMeta, err = schema.LookupPatchMetadataForSlice(originalKey)
1170 _, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies())
1174 // Check for consistency between the element order list and the field it applies to
1175 err = validatePatchWithSetOrderList(patchFieldValue, typedSetElementOrderList, patchMeta.GetPatchMergeKey())
1181 case foundOriginal && !foundPatch:
1182 // no change to list contents
1183 merged = originalFieldValue
1184 case !foundOriginal && foundPatch:
1186 merged = patchFieldValue
1187 case foundOriginal && foundPatch:
1188 merged, err = mergeSliceHandler(originalList, patchList, subschema,
1189 patchStrategy, patchMeta.GetPatchMergeKey(), false, mergeOptions)
1193 case !foundOriginal && !foundPatch:
1197 // Split all items into patch items and server-only items and then enforce the order.
1198 var patchItems, serverOnlyItems []interface{}
1199 if len(patchMeta.GetPatchMergeKey()) == 0 {
1200 // Primitives doesn't need merge key to do partitioning.
1201 patchItems, serverOnlyItems = partitionPrimitivesByPresentInList(merged, typedSetElementOrderList)
1204 // Maps need merge key to do partitioning.
1205 patchItems, serverOnlyItems, err = partitionMapsByPresentInList(merged, typedSetElementOrderList, patchMeta.GetPatchMergeKey())
1211 elementType, err := sliceElementType(originalFieldValue, patchFieldValue)
1215 kind := elementType.Kind()
1216 // normalize merged list
1217 // typedSetElementOrderList contains all the relative order in typedPatchList,
1218 // so don't need to use typedPatchList
1219 both, err := normalizeElementOrder(patchItems, serverOnlyItems, typedSetElementOrderList, originalFieldValue, patchMeta.GetPatchMergeKey(), kind)
1223 original[originalKey] = both
1224 // delete patch list from patch to prevent process again in the future
1225 delete(patch, originalKey)
1230 // partitionPrimitivesByPresentInList partitions elements into 2 slices, the first containing items present in partitionBy, the other not.
1231 func partitionPrimitivesByPresentInList(original, partitionBy []interface{}) ([]interface{}, []interface{}) {
1232 patch := make([]interface{}, 0, len(original))
1233 serverOnly := make([]interface{}, 0, len(original))
1234 inPatch := map[interface{}]bool{}
1235 for _, v := range partitionBy {
1238 for _, v := range original {
1240 serverOnly = append(serverOnly, v)
1242 patch = append(patch, v)
1245 return patch, serverOnly
1248 // partitionMapsByPresentInList partitions elements into 2 slices, the first containing items present in partitionBy, the other not.
1249 func partitionMapsByPresentInList(original, partitionBy []interface{}, mergeKey string) ([]interface{}, []interface{}, error) {
1250 patch := make([]interface{}, 0, len(original))
1251 serverOnly := make([]interface{}, 0, len(original))
1252 for _, v := range original {
1253 typedV, ok := v.(map[string]interface{})
1255 return nil, nil, mergepatch.ErrBadArgType(typedV, v)
1257 mergeKeyValue, foundMergeKey := typedV[mergeKey]
1259 return nil, nil, mergepatch.ErrNoMergeKey(typedV, mergeKey)
1261 _, _, found, err := findMapInSliceBasedOnKeyValue(partitionBy, mergeKey, mergeKeyValue)
1263 return nil, nil, err
1266 serverOnly = append(serverOnly, v)
1268 patch = append(patch, v)
1271 return patch, serverOnly, nil
1274 // Merge fields from a patch map into the original map. Note: This may modify
1275 // both the original map and the patch because getting a deep copy of a map in
1276 // golang is highly non-trivial.
1277 // flag mergeOptions.MergeParallelList controls if using the parallel list to delete or keeping the list.
1278 // If patch contains any null field (e.g. field_1: null) that is not
1279 // present in original, then to propagate it to the end result use
1280 // mergeOptions.IgnoreUnmatchedNulls == false.
1281 func mergeMap(original, patch map[string]interface{}, schema LookupPatchMeta, mergeOptions MergeOptions) (map[string]interface{}, error) {
1282 if v, ok := patch[directiveMarker]; ok {
1283 return handleDirectiveInMergeMap(v, patch)
1286 // nil is an accepted value for original to simplify logic in other places.
1287 // If original is nil, replace it with an empty map and then apply the patch.
1288 if original == nil {
1289 original = map[string]interface{}{}
1292 err := applyRetainKeysDirective(original, patch, mergeOptions)
1297 // Process $setElementOrder list and other lists sharing the same key.
1298 // When not merging the directive, it will make sure $setElementOrder list exist only in original.
1299 // When merging the directive, it will process $setElementOrder and its patch list together.
1300 // This function will delete the merged elements from patch so they will not be reprocessed
1301 err = mergePatchIntoOriginal(original, patch, schema, mergeOptions)
1306 // Start merging the patch into the original.
1307 for k, patchV := range patch {
1308 skipProcessing, isDeleteList, noPrefixKey, err := preprocessDeletionListForMerging(k, original, patchV, mergeOptions.MergeParallelList)
1315 if len(noPrefixKey) > 0 {
1319 // If the value of this key is null, delete the key if it exists in the
1320 // original. Otherwise, check if we want to preserve it or skip it.
1321 // Preserving the null value is useful when we want to send an explicit
1322 // delete to the API server.
1324 if _, ok := original[k]; ok {
1327 if mergeOptions.IgnoreUnmatchedNulls {
1332 _, ok := original[k]
1334 // If it's not in the original document, just take the patch value.
1335 original[k] = patchV
1339 originalType := reflect.TypeOf(original[k])
1340 patchType := reflect.TypeOf(patchV)
1341 if originalType != patchType {
1342 original[k] = patchV
1345 // If they're both maps or lists, recurse into the value.
1346 switch originalType.Kind() {
1348 subschema, patchMeta, err2 := schema.LookupPatchMetadataForStruct(k)
1352 _, patchStrategy, err2 := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies())
1356 original[k], err = mergeMapHandler(original[k], patchV, subschema, patchStrategy, mergeOptions)
1358 subschema, patchMeta, err2 := schema.LookupPatchMetadataForSlice(k)
1362 _, patchStrategy, err2 := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies())
1366 original[k], err = mergeSliceHandler(original[k], patchV, subschema, patchStrategy, patchMeta.GetPatchMergeKey(), isDeleteList, mergeOptions)
1368 original[k] = patchV
1374 return original, nil
1377 // mergeMapHandler handles how to merge `patchV` whose key is `key` with `original` respecting
1378 // fieldPatchStrategy and mergeOptions.
1379 func mergeMapHandler(original, patch interface{}, schema LookupPatchMeta,
1380 fieldPatchStrategy string, mergeOptions MergeOptions) (map[string]interface{}, error) {
1381 typedOriginal, typedPatch, err := mapTypeAssertion(original, patch)
1386 if fieldPatchStrategy != replaceDirective {
1387 return mergeMap(typedOriginal, typedPatch, schema, mergeOptions)
1389 return typedPatch, nil
1393 // mergeSliceHandler handles how to merge `patchV` whose key is `key` with `original` respecting
1394 // fieldPatchStrategy, fieldPatchMergeKey, isDeleteList and mergeOptions.
1395 func mergeSliceHandler(original, patch interface{}, schema LookupPatchMeta,
1396 fieldPatchStrategy, fieldPatchMergeKey string, isDeleteList bool, mergeOptions MergeOptions) ([]interface{}, error) {
1397 typedOriginal, typedPatch, err := sliceTypeAssertion(original, patch)
1402 if fieldPatchStrategy == mergeDirective {
1403 return mergeSlice(typedOriginal, typedPatch, schema, fieldPatchMergeKey, mergeOptions, isDeleteList)
1405 return typedPatch, nil
1409 // Merge two slices together. Note: This may modify both the original slice and
1410 // the patch because getting a deep copy of a slice in golang is highly
1412 func mergeSlice(original, patch []interface{}, schema LookupPatchMeta, mergeKey string, mergeOptions MergeOptions, isDeleteList bool) ([]interface{}, error) {
1413 if len(original) == 0 && len(patch) == 0 {
1414 return original, nil
1417 // All the values must be of the same type, but not a list.
1418 t, err := sliceElementType(original, patch)
1423 var merged []interface{}
1425 // If the elements are not maps, merge the slices of scalars.
1426 if kind != reflect.Map {
1427 if mergeOptions.MergeParallelList && isDeleteList {
1428 return deleteFromSlice(original, patch), nil
1430 // Maybe in the future add a "concat" mode that doesn't
1432 both := append(original, patch...)
1433 merged = deduplicateScalars(both)
1437 return nil, fmt.Errorf("cannot merge lists without merge key for %s", schema.Name())
1440 original, patch, err = mergeSliceWithSpecialElements(original, patch, mergeKey)
1445 merged, err = mergeSliceWithoutSpecialElements(original, patch, mergeKey, schema, mergeOptions)
1451 // enforce the order
1452 var patchItems, serverOnlyItems []interface{}
1453 if len(mergeKey) == 0 {
1454 patchItems, serverOnlyItems = partitionPrimitivesByPresentInList(merged, patch)
1456 patchItems, serverOnlyItems, err = partitionMapsByPresentInList(merged, patch, mergeKey)
1461 return normalizeElementOrder(patchItems, serverOnlyItems, patch, original, mergeKey, kind)
1464 // mergeSliceWithSpecialElements handles special elements with directiveMarker
1465 // before merging the slices. It returns a updated `original` and a patch without special elements.
1466 // original and patch must be slices of maps, they should be checked before calling this function.
1467 func mergeSliceWithSpecialElements(original, patch []interface{}, mergeKey string) ([]interface{}, []interface{}, error) {
1468 patchWithoutSpecialElements := []interface{}{}
1470 for _, v := range patch {
1471 typedV := v.(map[string]interface{})
1472 patchType, ok := typedV[directiveMarker]
1474 patchWithoutSpecialElements = append(patchWithoutSpecialElements, v)
1477 case deleteDirective:
1478 mergeValue, ok := typedV[mergeKey]
1481 original, err = deleteMatchingEntries(original, mergeKey, mergeValue)
1483 return nil, nil, err
1486 return nil, nil, mergepatch.ErrNoMergeKey(typedV, mergeKey)
1488 case replaceDirective:
1490 // Continue iterating through the array to prune any other $patch elements.
1491 case mergeDirective:
1492 return nil, nil, fmt.Errorf("merging lists cannot yet be specified in the patch")
1494 return nil, nil, mergepatch.ErrBadPatchType(patchType, typedV)
1499 return patchWithoutSpecialElements, nil, nil
1501 return original, patchWithoutSpecialElements, nil
1504 // delete all matching entries (based on merge key) from a merging list
1505 func deleteMatchingEntries(original []interface{}, mergeKey string, mergeValue interface{}) ([]interface{}, error) {
1507 _, originalKey, found, err := findMapInSliceBasedOnKeyValue(original, mergeKey, mergeValue)
1515 // Delete the element at originalKey.
1516 original = append(original[:originalKey], original[originalKey+1:]...)
1518 return original, nil
1521 // mergeSliceWithoutSpecialElements merges slices with non-special elements.
1522 // original and patch must be slices of maps, they should be checked before calling this function.
1523 func mergeSliceWithoutSpecialElements(original, patch []interface{}, mergeKey string, schema LookupPatchMeta, mergeOptions MergeOptions) ([]interface{}, error) {
1524 for _, v := range patch {
1525 typedV := v.(map[string]interface{})
1526 mergeValue, ok := typedV[mergeKey]
1528 return nil, mergepatch.ErrNoMergeKey(typedV, mergeKey)
1531 // If we find a value with this merge key value in original, merge the
1532 // maps. Otherwise append onto original.
1533 originalMap, originalKey, found, err := findMapInSliceBasedOnKeyValue(original, mergeKey, mergeValue)
1539 var mergedMaps interface{}
1541 // Merge into original.
1542 mergedMaps, err = mergeMap(originalMap, typedV, schema, mergeOptions)
1547 original[originalKey] = mergedMaps
1549 original = append(original, v)
1552 return original, nil
1555 // deleteFromSlice uses the parallel list to delete the items in a list of scalars
1556 func deleteFromSlice(current, toDelete []interface{}) []interface{} {
1557 toDeleteMap := map[interface{}]interface{}{}
1558 processed := make([]interface{}, 0, len(current))
1559 for _, v := range toDelete {
1560 toDeleteMap[v] = true
1562 for _, v := range current {
1563 if _, found := toDeleteMap[v]; !found {
1564 processed = append(processed, v)
1570 // This method no longer panics if any element of the slice is not a map.
1571 func findMapInSliceBasedOnKeyValue(m []interface{}, key string, value interface{}) (map[string]interface{}, int, bool, error) {
1572 for k, v := range m {
1573 typedV, ok := v.(map[string]interface{})
1575 return nil, 0, false, fmt.Errorf("value for key %v is not a map", k)
1578 valueToMatch, ok := typedV[key]
1579 if ok && valueToMatch == value {
1580 return typedV, k, true, nil
1584 return nil, 0, false, nil
1587 // This function takes a JSON map and sorts all the lists that should be merged
1588 // by key. This is needed by tests because in JSON, list order is significant,
1589 // but in Strategic Merge Patch, merge lists do not have significant order.
1590 // Sorting the lists allows for order-insensitive comparison of patched maps.
1591 func sortMergeListsByName(mapJSON []byte, schema LookupPatchMeta) ([]byte, error) {
1592 var m map[string]interface{}
1593 err := json.Unmarshal(mapJSON, &m)
1595 return nil, mergepatch.ErrBadJSONDoc
1598 newM, err := sortMergeListsByNameMap(m, schema)
1603 return json.Marshal(newM)
1606 // Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in a map.
1607 func sortMergeListsByNameMap(s map[string]interface{}, schema LookupPatchMeta) (map[string]interface{}, error) {
1608 newS := map[string]interface{}{}
1609 for k, v := range s {
1610 if k == retainKeysDirective {
1611 typedV, ok := v.([]interface{})
1613 return nil, mergepatch.ErrBadPatchFormatForRetainKeys
1615 v = sortScalars(typedV)
1616 } else if strings.HasPrefix(k, deleteFromPrimitiveListDirectivePrefix) {
1617 typedV, ok := v.([]interface{})
1619 return nil, mergepatch.ErrBadPatchFormatForPrimitiveList
1621 v = sortScalars(typedV)
1622 } else if strings.HasPrefix(k, setElementOrderDirectivePrefix) {
1623 _, ok := v.([]interface{})
1625 return nil, mergepatch.ErrBadPatchFormatForSetElementOrderList
1627 } else if k != directiveMarker {
1628 // recurse for map and slice.
1629 switch typedV := v.(type) {
1630 case map[string]interface{}:
1631 subschema, _, err := schema.LookupPatchMetadataForStruct(k)
1635 v, err = sortMergeListsByNameMap(typedV, subschema)
1640 subschema, patchMeta, err := schema.LookupPatchMetadataForSlice(k)
1644 _, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies())
1648 if patchStrategy == mergeDirective {
1650 v, err = sortMergeListsByNameArray(typedV, subschema, patchMeta.GetPatchMergeKey(), true)
1664 // Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in an array.
1665 func sortMergeListsByNameArray(s []interface{}, schema LookupPatchMeta, mergeKey string, recurse bool) ([]interface{}, error) {
1670 // We don't support lists of lists yet.
1671 t, err := sliceElementType(s)
1676 // If the elements are not maps...
1677 if t.Kind() != reflect.Map {
1678 // Sort the elements, because they may have been merged out of order.
1679 return deduplicateAndSortScalars(s), nil
1682 // Elements are maps - if one of the keys of the map is a map or a
1683 // list, we may need to recurse into it.
1684 newS := []interface{}{}
1685 for _, elem := range s {
1687 typedElem := elem.(map[string]interface{})
1688 newElem, err := sortMergeListsByNameMap(typedElem, schema)
1693 newS = append(newS, newElem)
1695 newS = append(newS, elem)
1700 newS = sortMapsBasedOnField(newS, mergeKey)
1704 func sortMapsBasedOnField(m []interface{}, fieldName string) []interface{} {
1705 mapM := mapSliceFromSlice(m)
1706 ss := SortableSliceOfMaps{mapM, fieldName}
1708 newS := sliceFromMapSlice(ss.s)
1712 func mapSliceFromSlice(m []interface{}) []map[string]interface{} {
1713 newM := []map[string]interface{}{}
1714 for _, v := range m {
1715 vt := v.(map[string]interface{})
1716 newM = append(newM, vt)
1722 func sliceFromMapSlice(s []map[string]interface{}) []interface{} {
1723 newS := []interface{}{}
1724 for _, v := range s {
1725 newS = append(newS, v)
1731 type SortableSliceOfMaps struct {
1732 s []map[string]interface{}
1733 k string // key to sort on
1736 func (ss SortableSliceOfMaps) Len() int {
1740 func (ss SortableSliceOfMaps) Less(i, j int) bool {
1741 iStr := fmt.Sprintf("%v", ss.s[i][ss.k])
1742 jStr := fmt.Sprintf("%v", ss.s[j][ss.k])
1743 return sort.StringsAreSorted([]string{iStr, jStr})
1746 func (ss SortableSliceOfMaps) Swap(i, j int) {
1752 func deduplicateAndSortScalars(s []interface{}) []interface{} {
1753 s = deduplicateScalars(s)
1754 return sortScalars(s)
1757 func sortScalars(s []interface{}) []interface{} {
1758 ss := SortableSliceOfScalars{s}
1763 func deduplicateScalars(s []interface{}) []interface{} {
1764 // Clever algorithm to deduplicate.
1765 length := len(s) - 1
1766 for i := 0; i < length; i++ {
1767 for j := i + 1; j <= length; j++ {
1780 type SortableSliceOfScalars struct {
1784 func (ss SortableSliceOfScalars) Len() int {
1788 func (ss SortableSliceOfScalars) Less(i, j int) bool {
1789 iStr := fmt.Sprintf("%v", ss.s[i])
1790 jStr := fmt.Sprintf("%v", ss.s[j])
1791 return sort.StringsAreSorted([]string{iStr, jStr})
1794 func (ss SortableSliceOfScalars) Swap(i, j int) {
1800 // Returns the type of the elements of N slice(s). If the type is different,
1801 // another slice or undefined, returns an error.
1802 func sliceElementType(slices ...[]interface{}) (reflect.Type, error) {
1803 var prevType reflect.Type
1804 for _, s := range slices {
1805 // Go through elements of all given slices and make sure they are all the same type.
1806 for _, v := range s {
1807 currentType := reflect.TypeOf(v)
1808 if prevType == nil {
1809 prevType = currentType
1810 // We don't support lists of lists yet.
1811 if prevType.Kind() == reflect.Slice {
1812 return nil, mergepatch.ErrNoListOfLists
1815 if prevType != currentType {
1816 return nil, fmt.Errorf("list element types are not identical: %v", fmt.Sprint(slices))
1818 prevType = currentType
1823 if prevType == nil {
1824 return nil, fmt.Errorf("no elements in any of the given slices")
1827 return prevType, nil
1830 // MergingMapsHaveConflicts returns true if the left and right JSON interface
1831 // objects overlap with different values in any key. All keys are required to be
1832 // strings. Since patches of the same Type have congruent keys, this is valid
1833 // for multiple patch types. This method supports strategic merge patch semantics.
1834 func MergingMapsHaveConflicts(left, right map[string]interface{}, schema LookupPatchMeta) (bool, error) {
1835 return mergingMapFieldsHaveConflicts(left, right, schema, "", "")
1838 func mergingMapFieldsHaveConflicts(
1839 left, right interface{},
1840 schema LookupPatchMeta,
1841 fieldPatchStrategy, fieldPatchMergeKey string,
1843 switch leftType := left.(type) {
1844 case map[string]interface{}:
1845 rightType, ok := right.(map[string]interface{})
1849 leftMarker, okLeft := leftType[directiveMarker]
1850 rightMarker, okRight := rightType[directiveMarker]
1851 // if one or the other has a directive marker,
1852 // then we need to consider that before looking at the individual keys,
1853 // since a directive operates on the whole map.
1854 if okLeft || okRight {
1855 // if one has a directive marker and the other doesn't,
1856 // then we have a conflict, since one is deleting or replacing the whole map,
1857 // and the other is doing things to individual keys.
1858 if okLeft != okRight {
1861 // if they both have markers, but they are not the same directive,
1862 // then we have a conflict because they're doing different things to the map.
1863 if leftMarker != rightMarker {
1867 if fieldPatchStrategy == replaceDirective {
1870 // Check the individual keys.
1871 return mapsHaveConflicts(leftType, rightType, schema)
1874 rightType, ok := right.([]interface{})
1878 return slicesHaveConflicts(leftType, rightType, schema, fieldPatchStrategy, fieldPatchMergeKey)
1879 case string, float64, bool, int64, nil:
1880 return !reflect.DeepEqual(left, right), nil
1882 return true, fmt.Errorf("unknown type: %v", reflect.TypeOf(left))
1886 func mapsHaveConflicts(typedLeft, typedRight map[string]interface{}, schema LookupPatchMeta) (bool, error) {
1887 for key, leftValue := range typedLeft {
1888 if key != directiveMarker && key != retainKeysDirective {
1889 if rightValue, ok := typedRight[key]; ok {
1890 var subschema LookupPatchMeta
1891 var patchMeta PatchMeta
1892 var patchStrategy string
1894 switch leftValue.(type) {
1896 subschema, patchMeta, err = schema.LookupPatchMetadataForSlice(key)
1900 _, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.patchStrategies)
1904 case map[string]interface{}:
1905 subschema, patchMeta, err = schema.LookupPatchMetadataForStruct(key)
1909 _, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.patchStrategies)
1915 if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue,
1916 subschema, patchStrategy, patchMeta.GetPatchMergeKey()); hasConflicts {
1926 func slicesHaveConflicts(
1927 typedLeft, typedRight []interface{},
1928 schema LookupPatchMeta,
1929 fieldPatchStrategy, fieldPatchMergeKey string,
1931 elementType, err := sliceElementType(typedLeft, typedRight)
1936 if fieldPatchStrategy == mergeDirective {
1937 // Merging lists of scalars have no conflicts by definition
1938 // So we only need to check further if the elements are maps
1939 if elementType.Kind() != reflect.Map {
1943 // Build a map for each slice and then compare the two maps
1944 leftMap, err := sliceOfMapsToMapOfMaps(typedLeft, fieldPatchMergeKey)
1949 rightMap, err := sliceOfMapsToMapOfMaps(typedRight, fieldPatchMergeKey)
1954 return mapsOfMapsHaveConflicts(leftMap, rightMap, schema)
1957 // Either we don't have type information, or these are non-merging lists
1958 if len(typedLeft) != len(typedRight) {
1962 // Sort scalar slices to prevent ordering issues
1963 // We have no way to sort non-merging lists of maps
1964 if elementType.Kind() != reflect.Map {
1965 typedLeft = deduplicateAndSortScalars(typedLeft)
1966 typedRight = deduplicateAndSortScalars(typedRight)
1969 // Compare the slices element by element in order
1970 // This test will fail if the slices are not sorted
1971 for i := range typedLeft {
1972 if hasConflicts, err := mergingMapFieldsHaveConflicts(typedLeft[i], typedRight[i], schema, "", ""); hasConflicts {
1980 func sliceOfMapsToMapOfMaps(slice []interface{}, mergeKey string) (map[string]interface{}, error) {
1981 result := make(map[string]interface{}, len(slice))
1982 for _, value := range slice {
1983 typedValue, ok := value.(map[string]interface{})
1985 return nil, fmt.Errorf("invalid element type in merging list:%v", slice)
1988 mergeValue, ok := typedValue[mergeKey]
1990 return nil, fmt.Errorf("cannot find merge key `%s` in merging list element:%v", mergeKey, typedValue)
1993 result[fmt.Sprintf("%s", mergeValue)] = typedValue
1999 func mapsOfMapsHaveConflicts(typedLeft, typedRight map[string]interface{}, schema LookupPatchMeta) (bool, error) {
2000 for key, leftValue := range typedLeft {
2001 if rightValue, ok := typedRight[key]; ok {
2002 if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue, schema, "", ""); hasConflicts {
2011 // CreateThreeWayMergePatch reconciles a modified configuration with an original configuration,
2012 // while preserving any changes or deletions made to the original configuration in the interim,
2013 // and not overridden by the current configuration. All three documents must be passed to the
2014 // method as json encoded content. It will return a strategic merge patch, or an error if any
2015 // of the documents is invalid, or if there are any preconditions that fail against the modified
2016 // configuration, or, if overwrite is false and there are conflicts between the modified and current
2017 // configurations. Conflicts are defined as keys changed differently from original to modified
2018 // than from original to current. In other words, a conflict occurs if modified changes any key
2019 // in a way that is different from how it is changed in current (e.g., deleting it, changing its
2020 // value). We also propagate values fields that do not exist in original but are explicitly
2021 // defined in modified.
2022 func CreateThreeWayMergePatch(original, modified, current []byte, schema LookupPatchMeta, overwrite bool, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
2023 originalMap := map[string]interface{}{}
2024 if len(original) > 0 {
2025 if err := json.Unmarshal(original, &originalMap); err != nil {
2026 return nil, mergepatch.ErrBadJSONDoc
2030 modifiedMap := map[string]interface{}{}
2031 if len(modified) > 0 {
2032 if err := json.Unmarshal(modified, &modifiedMap); err != nil {
2033 return nil, mergepatch.ErrBadJSONDoc
2037 currentMap := map[string]interface{}{}
2038 if len(current) > 0 {
2039 if err := json.Unmarshal(current, ¤tMap); err != nil {
2040 return nil, mergepatch.ErrBadJSONDoc
2044 // The patch is the difference from current to modified without deletions, plus deletions
2045 // from original to modified. To find it, we compute deletions, which are the deletions from
2046 // original to modified, and delta, which is the difference from current to modified without
2047 // deletions, and then apply delta to deletions as a patch, which should be strictly additive.
2048 deltaMapDiffOptions := DiffOptions{
2049 IgnoreDeletions: true,
2050 SetElementOrder: true,
2052 deltaMap, err := diffMaps(currentMap, modifiedMap, schema, deltaMapDiffOptions)
2056 deletionsMapDiffOptions := DiffOptions{
2057 SetElementOrder: true,
2058 IgnoreChangesAndAdditions: true,
2060 deletionsMap, err := diffMaps(originalMap, modifiedMap, schema, deletionsMapDiffOptions)
2065 mergeOptions := MergeOptions{}
2066 patchMap, err := mergeMap(deletionsMap, deltaMap, schema, mergeOptions)
2071 // Apply the preconditions to the patch, and return an error if any of them fail.
2072 for _, fn := range fns {
2074 return nil, mergepatch.NewErrPreconditionFailed(patchMap)
2078 // If overwrite is false, and the patch contains any keys that were changed differently,
2079 // then return a conflict error.
2081 changeMapDiffOptions := DiffOptions{}
2082 changedMap, err := diffMaps(originalMap, currentMap, schema, changeMapDiffOptions)
2087 hasConflicts, err := MergingMapsHaveConflicts(patchMap, changedMap, schema)
2093 return nil, mergepatch.NewErrConflict(mergepatch.ToYAMLOrError(patchMap), mergepatch.ToYAMLOrError(changedMap))
2097 return json.Marshal(patchMap)
2100 func ItemAddedToModifiedSlice(original, modified string) bool { return original > modified }
2102 func ItemRemovedFromModifiedSlice(original, modified string) bool { return original < modified }
2104 func ItemMatchesOriginalAndModifiedSlice(original, modified string) bool { return original == modified }
2106 func CreateDeleteDirective(mergeKey string, mergeKeyValue interface{}) map[string]interface{} {
2107 return map[string]interface{}{mergeKey: mergeKeyValue, directiveMarker: deleteDirective}
2110 func mapTypeAssertion(original, patch interface{}) (map[string]interface{}, map[string]interface{}, error) {
2111 typedOriginal, ok := original.(map[string]interface{})
2113 return nil, nil, mergepatch.ErrBadArgType(typedOriginal, original)
2115 typedPatch, ok := patch.(map[string]interface{})
2117 return nil, nil, mergepatch.ErrBadArgType(typedPatch, patch)
2119 return typedOriginal, typedPatch, nil
2122 func sliceTypeAssertion(original, patch interface{}) ([]interface{}, []interface{}, error) {
2123 typedOriginal, ok := original.([]interface{})
2125 return nil, nil, mergepatch.ErrBadArgType(typedOriginal, original)
2127 typedPatch, ok := patch.([]interface{})
2129 return nil, nil, mergepatch.ErrBadArgType(typedPatch, patch)
2131 return typedOriginal, typedPatch, nil
2134 // extractRetainKeysPatchStrategy process patch strategy, which is a string may contains multiple
2135 // patch strategies separated by ",". It returns a boolean var indicating if it has
2136 // retainKeys strategies and a string for the other strategy.
2137 func extractRetainKeysPatchStrategy(strategies []string) (bool, string, error) {
2138 switch len(strategies) {
2140 return false, "", nil
2142 singleStrategy := strategies[0]
2143 switch singleStrategy {
2144 case retainKeysStrategy:
2145 return true, "", nil
2147 return false, singleStrategy, nil
2151 case strategies[0] == retainKeysStrategy:
2152 return true, strategies[1], nil
2153 case strategies[1] == retainKeysStrategy:
2154 return true, strategies[0], nil
2156 return false, "", fmt.Errorf("unexpected patch strategy: %v", strategies)
2159 return false, "", fmt.Errorf("unexpected patch strategy: %v", strategies)
2163 // hasAdditionalNewField returns if original map has additional key with non-nil value than modified.
2164 func hasAdditionalNewField(original, modified map[string]interface{}) bool {
2165 for k, v := range original {
2169 if _, found := modified[k]; !found {