const directiveMarker … const deleteDirective … const replaceDirective … const mergeDirective … const retainKeysStrategy … const deleteFromPrimitiveListDirectivePrefix … const retainKeysDirective … const setElementOrderDirectivePrefix … type JSONMap … type DiffOptions … type MergeOptions … // CreateTwoWayMergePatch creates a patch that can be passed to StrategicMergePatch from an original // document and a modified document, which are passed to the method as json encoded content. It will // return a patch that yields the modified document when applied to the original document, or an error // if either of the two documents is invalid. func CreateTwoWayMergePatch(original, modified []byte, dataStruct interface{ … } func CreateTwoWayMergePatchUsingLookupPatchMeta( original, modified []byte, schema LookupPatchMeta, fns ...mergepatch.PreconditionFunc) ([]byte, error) { … } // CreateTwoWayMergeMapPatch creates a patch from an original and modified JSON objects, // encoded JSONMap. // The serialized version of the map can then be passed to StrategicMergeMapPatch. func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{ … } func CreateTwoWayMergeMapPatchUsingLookupPatchMeta(original, modified JSONMap, schema LookupPatchMeta, fns ...mergepatch.PreconditionFunc) (JSONMap, error) { … } // Returns a (recursive) strategic merge patch that yields modified when applied to original. // Including: // - Adding fields to the patch present in modified, missing from original // - Setting fields to the patch present in modified and original with different values // - Delete fields present in original, missing from modified through // - IFF map field - set to nil in patch // - IFF list of maps && merge strategy - use deleteDirective for the elements // - IFF list of primitives && merge strategy - use parallel deletion list // - IFF list of maps or primitives with replace strategy (default) - set patch value to the value in modified // - Build $retainKeys directive for fields with retainKeys patch strategy func diffMaps(original, modified map[string]interface{ … } // handleDirectiveMarker handles how to diff directive marker between 2 objects func handleDirectiveMarker(key string, originalValue, modifiedValue interface{ … } // handleMapDiff diff between 2 maps `originalValueTyped` and `modifiedValue`, // puts the diff in the `patch` associated with `key` // key is the key associated with originalValue and modifiedValue. // originalValue, modifiedValue are the old and new value respectively.They are both maps // patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue // diffOptions contains multiple options to control how we do the diff. func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]interface{ … } // handleSliceDiff diff between 2 slices `originalValueTyped` and `modifiedValue`, // puts the diff in the `patch` associated with `key` // key is the key associated with originalValue and modifiedValue. // originalValue, modifiedValue are the old and new value respectively.They are both slices // patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue // diffOptions contains multiple options to control how we do the diff. func handleSliceDiff(key string, originalValue, modifiedValue []interface{ … } // replacePatchFieldIfNotEqual updates the patch if original and modified are not deep equal // if diffOptions.IgnoreChangesAndAdditions is false. // original is the old value, maybe either the live cluster object or the last applied configuration // modified is the new value, is always the users new config func replacePatchFieldIfNotEqual(key string, original, modified interface{ … } // updatePatchIfMissing iterates over `original` when ignoreDeletions is false. // Clear the field whose key is not present in `modified`. // original is the old value, maybe either the live cluster object or the last applied configuration // modified is the new value, is always the users new config func updatePatchIfMissing(original, modified, patch map[string]interface{ … } // validateMergeKeyInLists checks if each map in the list has the mentryerge key. func validateMergeKeyInLists(mergeKey string, lists ...[]interface{ … } // normalizeElementOrder sort `patch` list by `patchOrder` and sort `serverOnly` list by `serverOrder`. // Then it merges the 2 sorted lists. // It guarantee the relative order in the patch list and in the serverOnly list is kept. // `patch` is a list of items in the patch, and `serverOnly` is a list of items in the live object. // `patchOrder` is the order we want `patch` list to have and // `serverOrder` is the order we want `serverOnly` list to have. // kind is the kind of each item in the lists `patch` and `serverOnly`. func normalizeElementOrder(patch, serverOnly, patchOrder, serverOrder []interface{ … } // mergeSortedSlice merges the 2 sorted lists by serverOrder with best effort. // It will insert each item in `left` list to `right` list. In most cases, the 2 lists will be interleaved. // The relative order of left and right are guaranteed to be kept. // They have higher precedence than the order in the live list. // The place for a item in `left` is found by: // scan from the place of last insertion in `right` to the end of `right`, // the place is before the first item that is greater than the item we want to insert. // example usage: using server-only items as left and patch items as right. We insert server-only items // to patch list. We use the order of live object as record for comparison. func mergeSortedSlice(left, right, serverOrder []interface{ … } // index returns the index of the item in the given items, or -1 if it doesn't exist // l must NOT be a slice of slices, this should be checked before calling. func index(l []interface{ … } // extractToDeleteItems takes a list and // returns 2 lists: one contains items that should be kept and the other contains items to be deleted. func extractToDeleteItems(l []interface{ … } // normalizeSliceOrder sort `toSort` list by `order` func normalizeSliceOrder(toSort, order []interface{ … } // Returns a (recursive) strategic merge patch, a parallel deletion list if necessary and // another list to set the order of the list // Only list of primitives with merge strategy will generate a parallel deletion list. // These two lists should yield modified when applied to original, for lists with merge semantics. func diffLists(original, modified []interface{ … } // isOrderSame checks if the order in a list has changed func isOrderSame(original, modified []interface{ … } // diffListsOfScalars returns 2 lists, the first one is addList and the second one is deletionList. // Argument diffOptions.IgnoreChangesAndAdditions controls if calculate addList. true means not calculate. // Argument diffOptions.IgnoreDeletions controls if calculate deletionList. true means not calculate. // original may be changed, but modified is guaranteed to not be changed func diffListsOfScalars(original, modified []interface{ … } // If first return value is non-nil, list1 contains an element not present in list2 // If second return value is non-nil, list2 contains an element not present in list1 func compareListValuesAtIndex(list1Inbounds, list2Inbounds bool, list1Value, list2Value string) (interface{ … } // diffListsOfMaps takes a pair of lists and // returns a (recursive) strategic merge patch list contains additions and changes and // a deletion list contains deletions func diffListsOfMaps(original, modified []interface{ … } // getMapAndMergeKeyValueByIndex return a map in the list and its merge key value given the index of the map. func getMapAndMergeKeyValueByIndex(index int, mergeKey string, listOfMaps []interface{ … } // StrategicMergePatch applies a strategic merge patch. The patch and the original document // must be json encoded content. A patch can be created from an original and a modified document // by calling CreateStrategicMergePatch. func StrategicMergePatch(original, patch []byte, dataStruct interface{ … } func StrategicMergePatchUsingLookupPatchMeta(original, patch []byte, schema LookupPatchMeta) ([]byte, error) { … } func handleUnmarshal(j []byte) (map[string]interface{ … } // StrategicMergeMapPatch applies a strategic merge patch. The original and patch documents // must be JSONMap. A patch can be created from an original and modified document by // calling CreateTwoWayMergeMapPatch. // Warning: the original and patch JSONMap objects are mutated by this function and should not be reused. func StrategicMergeMapPatch(original, patch JSONMap, dataStruct interface{ … } func StrategicMergeMapPatchUsingLookupPatchMeta(original, patch JSONMap, schema LookupPatchMeta) (JSONMap, error) { … } // MergeStrategicMergeMapPatchUsingLookupPatchMeta merges strategic merge // patches retaining `null` fields and parallel lists. If 2 patches change the // same fields and the latter one will override the former one. If you don't // want that happen, you need to run func MergingMapsHaveConflicts before // merging these patches. Applying the resulting merged merge patch to a JSONMap // yields the same as merging each strategic merge patch to the JSONMap in // succession. func MergeStrategicMergeMapPatchUsingLookupPatchMeta(schema LookupPatchMeta, patches ...JSONMap) (JSONMap, error) { … } // handleDirectiveInMergeMap handles the patch directive when merging 2 maps. func handleDirectiveInMergeMap(directive interface{ … } func containsDirectiveMarker(item interface{ … } func mergeKeyValueEqual(left, right interface{ … } // extractKey trims the prefix and return the original key func extractKey(s, prefix string) (string, error) { … } // validatePatchUsingSetOrderList verifies: // the relative order of any two items in the setOrderList list matches that in the patch list. // the items in the patch list must be a subset or the same as the $setElementOrder list (deletions are ignored). func validatePatchWithSetOrderList(patchList, setOrderList interface{ … } // preprocessDeletionListForMerging preprocesses the deletion list. // it returns shouldContinue, isDeletionList, noPrefixKey func preprocessDeletionListForMerging(key string, original map[string]interface{ … } // applyRetainKeysDirective looks for a retainKeys directive and applies to original // - if no directive exists do nothing // - if directive is found, clear keys in original missing from the directive list // - validate that all keys present in the patch are present in the retainKeys directive // 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 func applyRetainKeysDirective(original, patch map[string]interface{ … } // mergePatchIntoOriginal processes $setElementOrder list. // When not merging the directive, it will make sure $setElementOrder list exist only in original. // When merging the directive, it will try to find the $setElementOrder list and // its corresponding patch list, validate it and merge it. // Then, sort them by the relative order in setElementOrder, patch list and live list. // The precedence is $setElementOrder > order in patch list > order in live list. // This function will delete the item after merging it to prevent process it again in the future. // Ref: https://git.k8s.io/design-proposals-archive/cli/preserve-order-in-strategic-merge-patch.md func mergePatchIntoOriginal(original, patch map[string]interface{ … } // partitionPrimitivesByPresentInList partitions elements into 2 slices, the first containing items present in partitionBy, the other not. func partitionPrimitivesByPresentInList(original, partitionBy []interface{ … } // partitionMapsByPresentInList partitions elements into 2 slices, the first containing items present in partitionBy, the other not. func partitionMapsByPresentInList(original, partitionBy []interface{ … } // Removes directives from an object and returns value to use instead and whether // or not the field/index should even be kept // May modify input func removeDirectives(obj interface{ … } // Merge fields from a patch map into the original map. Note: This may modify // both the original map and the patch because getting a deep copy of a map in // golang is highly non-trivial. // flag mergeOptions.MergeParallelList controls if using the parallel list to delete or keeping the list. // If patch contains any null field (e.g. field_1: null) that is not // present in original, then to propagate it to the end result use // mergeOptions.IgnoreUnmatchedNulls == false. func mergeMap(original, patch map[string]interface{ … } // discardNullValuesFromPatch discards all null property values from patch. // It traverses all slices and map types. func discardNullValuesFromPatch(patchV interface{ … } // mergeMapHandler handles how to merge `patchV` whose key is `key` with `original` respecting // fieldPatchStrategy and mergeOptions. func mergeMapHandler(original, patch interface{ … } // mergeSliceHandler handles how to merge `patchV` whose key is `key` with `original` respecting // fieldPatchStrategy, fieldPatchMergeKey, isDeleteList and mergeOptions. func mergeSliceHandler(original, patch interface{ … } // Merge two slices together. Note: This may modify both the original slice and // the patch because getting a deep copy of a slice in golang is highly // non-trivial. func mergeSlice(original, patch []interface{ … } // mergeSliceWithSpecialElements handles special elements with directiveMarker // before merging the slices. It returns a updated `original` and a patch without special elements. // original and patch must be slices of maps, they should be checked before calling this function. func mergeSliceWithSpecialElements(original, patch []interface{ … } // delete all matching entries (based on merge key) from a merging list func deleteMatchingEntries(original []interface{ … } // mergeSliceWithoutSpecialElements merges slices with non-special elements. // original and patch must be slices of maps, they should be checked before calling this function. func mergeSliceWithoutSpecialElements(original, patch []interface{ … } // deleteFromSlice uses the parallel list to delete the items in a list of scalars func deleteFromSlice(current, toDelete []interface{ … } // This method no longer panics if any element of the slice is not a map. func findMapInSliceBasedOnKeyValue(m []interface{ … } // This function takes a JSON map and sorts all the lists that should be merged // by key. This is needed by tests because in JSON, list order is significant, // but in Strategic Merge Patch, merge lists do not have significant order. // Sorting the lists allows for order-insensitive comparison of patched maps. func sortMergeListsByName(mapJSON []byte, schema LookupPatchMeta) ([]byte, error) { … } // Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in a map. func sortMergeListsByNameMap(s map[string]interface{ … } // Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in an array. func sortMergeListsByNameArray(s []interface{ … } func sortMapsBasedOnField(m []interface{ … } func mapSliceFromSlice(m []interface{ … } func sliceFromMapSlice(s []map[string]interface{ … } type SortableSliceOfMaps … func (ss SortableSliceOfMaps) Len() int { … } func (ss SortableSliceOfMaps) Less(i, j int) bool { … } func (ss SortableSliceOfMaps) Swap(i, j int) { … } func deduplicateAndSortScalars(s []interface{ … } func sortScalars(s []interface{ … } func deduplicateScalars(s []interface{ … } type SortableSliceOfScalars … func (ss SortableSliceOfScalars) Len() int { … } func (ss SortableSliceOfScalars) Less(i, j int) bool { … } func (ss SortableSliceOfScalars) Swap(i, j int) { … } // Returns the type of the elements of N slice(s). If the type is different, // another slice or undefined, returns an error. func sliceElementType(slices ...[]interface{ … } // MergingMapsHaveConflicts returns true if the left and right JSON interface // objects overlap with different values in any key. All keys are required to be // strings. Since patches of the same Type have congruent keys, this is valid // for multiple patch types. This method supports strategic merge patch semantics. func MergingMapsHaveConflicts(left, right map[string]interface{ … } func mergingMapFieldsHaveConflicts( left, right interface{ … } func mapsHaveConflicts(typedLeft, typedRight map[string]interface{ … } func slicesHaveConflicts( typedLeft, typedRight []interface{ … } func sliceOfMapsToMapOfMaps(slice []interface{ … } func mapsOfMapsHaveConflicts(typedLeft, typedRight map[string]interface{ … } // CreateThreeWayMergePatch reconciles a modified configuration with an original configuration, // while preserving any changes or deletions made to the original configuration in the interim, // and not overridden by the current configuration. All three documents must be passed to the // method as json encoded content. It will return a strategic merge patch, or an error if any // of the documents is invalid, or if there are any preconditions that fail against the modified // configuration, or, if overwrite is false and there are conflicts between the modified and current // configurations. Conflicts are defined as keys changed differently from original to modified // than from original to current. In other words, a conflict occurs if modified changes any key // in a way that is different from how it is changed in current (e.g., deleting it, changing its // value). We also propagate values fields that do not exist in original but are explicitly // defined in modified. func CreateThreeWayMergePatch(original, modified, current []byte, schema LookupPatchMeta, overwrite bool, fns ...mergepatch.PreconditionFunc) ([]byte, error) { … } func ItemAddedToModifiedSlice(original, modified string) bool { … } func ItemRemovedFromModifiedSlice(original, modified string) bool { … } func ItemMatchesOriginalAndModifiedSlice(original, modified string) bool { … } func CreateDeleteDirective(mergeKey string, mergeKeyValue interface{ … } func mapTypeAssertion(original, patch interface{ … } func sliceTypeAssertion(original, patch interface{ … } // extractRetainKeysPatchStrategy process patch strategy, which is a string may contains multiple // patch strategies separated by ",". It returns a boolean var indicating if it has // retainKeys strategies and a string for the other strategy. func extractRetainKeysPatchStrategy(strategies []string) (bool, string, error) { … } // hasAdditionalNewField returns if original map has additional key with non-nil value than modified. func hasAdditionalNewField(original, modified map[string]interface{ … }