mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 23:15:14 +00:00
Merge pull request #13820 from jackgr/apply_patch
Add method to apply strategic merge patch
This commit is contained in:
commit
f9364da306
@ -35,27 +35,100 @@ import (
|
||||
// Some of the content of this package was borrowed with minor adaptations from
|
||||
// evanphx/json-patch and openshift/origin.
|
||||
|
||||
const specialKey = "$patch"
|
||||
const specialValue = "delete"
|
||||
const (
|
||||
directiveMarker = "$patch"
|
||||
deleteDirective = "delete"
|
||||
replaceDirective = "replace"
|
||||
mergeDirective = "merge"
|
||||
)
|
||||
|
||||
// IsPreconditionFailed returns true if the provided error indicates
|
||||
// a precondition failed.
|
||||
func IsPreconditionFailed(err error) bool {
|
||||
_, ok := err.(errPreconditionFailed)
|
||||
return ok
|
||||
}
|
||||
|
||||
type errPreconditionFailed struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func newErrPreconditionFailed(target map[string]interface{}) errPreconditionFailed {
|
||||
s := fmt.Sprintf("precondition failed for: %v", target)
|
||||
return errPreconditionFailed{s}
|
||||
}
|
||||
|
||||
func (err errPreconditionFailed) Error() string {
|
||||
return err.message
|
||||
}
|
||||
|
||||
type errConflict struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func newErrConflict(patch, current []byte) errConflict {
|
||||
s := fmt.Sprintf("patch:\n%s\nconflicts with current:\n%s\n", patch, current)
|
||||
return errConflict{s}
|
||||
}
|
||||
|
||||
func (err errConflict) Error() string {
|
||||
return err.message
|
||||
}
|
||||
|
||||
// IsConflict returns true if the provided error indicates
|
||||
// a conflict between the patch and the current configuration.
|
||||
func IsConflict(err error) bool {
|
||||
_, ok := err.(errConflict)
|
||||
return ok
|
||||
}
|
||||
|
||||
var errBadJSONDoc = fmt.Errorf("Invalid JSON document")
|
||||
var errNoListOfLists = fmt.Errorf("Lists of lists are not supported")
|
||||
|
||||
// CreateStrategicMergePatch creates a patch that can be passed to StrategicMergePatch.
|
||||
// The original and modified documents must be passed to the method as json encoded content.
|
||||
// It will return a mergeable json document with differences from original to modified, or an error
|
||||
// if either of the two documents is invalid.
|
||||
// The following code is adapted from github.com/openshift/origin/pkg/util/jsonmerge.
|
||||
// Instead of defining a Delta that holds an original, a patch and a set of preconditions,
|
||||
// the reconcile method accepts a set of preconditions as an argument.
|
||||
|
||||
// PreconditionFunc asserts that an incompatible change is not present within a patch.
|
||||
type PreconditionFunc func(interface{}) bool
|
||||
|
||||
// RequireKeyUnchanged returns a precondition function that fails if the provided key
|
||||
// is present in the patch (indicating that its value has changed).
|
||||
func RequireKeyUnchanged(key string) PreconditionFunc {
|
||||
return func(patch interface{}) bool {
|
||||
patchMap, ok := patch.(map[string]interface{})
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// The presence of key means that its value has been changed, so the test fails.
|
||||
_, ok = patchMap[key]
|
||||
return !ok
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: Use the synonym CreateTwoWayMergePatch, instead.
|
||||
func CreateStrategicMergePatch(original, modified []byte, dataStruct interface{}) ([]byte, error) {
|
||||
return CreateTwoWayMergePatch(original, modified, dataStruct)
|
||||
}
|
||||
|
||||
// CreateTwoWayMergePatch creates a patch that can be passed to StrategicMergePatch from an original
|
||||
// document and a modified documernt, 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{}, fns ...PreconditionFunc) ([]byte, error) {
|
||||
originalMap := map[string]interface{}{}
|
||||
err := json.Unmarshal(original, &originalMap)
|
||||
if err != nil {
|
||||
return nil, errBadJSONDoc
|
||||
if len(original) > 0 {
|
||||
if err := json.Unmarshal(original, &originalMap); err != nil {
|
||||
return nil, errBadJSONDoc
|
||||
}
|
||||
}
|
||||
|
||||
modifiedMap := map[string]interface{}{}
|
||||
err = json.Unmarshal(modified, &modifiedMap)
|
||||
if err != nil {
|
||||
return nil, errBadJSONDoc
|
||||
if len(modified) > 0 {
|
||||
if err := json.Unmarshal(modified, &modifiedMap); err != nil {
|
||||
return nil, errBadJSONDoc
|
||||
}
|
||||
}
|
||||
|
||||
t, err := getTagStructType(dataStruct)
|
||||
@ -68,11 +141,18 @@ func CreateStrategicMergePatch(original, modified []byte, dataStruct interface{}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply the preconditions to the patch, and return an error if any of them fail.
|
||||
for _, fn := range fns {
|
||||
if !fn(patchMap) {
|
||||
return nil, newErrPreconditionFailed(patchMap)
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(patchMap)
|
||||
}
|
||||
|
||||
// Returns a (recursive) strategic merge patch that yields modified when applied to original.
|
||||
func diffMaps(original, modified map[string]interface{}, t reflect.Type, ignoreAdditions, ignoreChangesAndDeletions bool) (map[string]interface{}, error) {
|
||||
func diffMaps(original, modified map[string]interface{}, t reflect.Type, ignoreChangesAndAdditions, ignoreDeletions bool) (map[string]interface{}, error) {
|
||||
patch := map[string]interface{}{}
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
@ -80,42 +160,43 @@ func diffMaps(original, modified map[string]interface{}, t reflect.Type, ignoreA
|
||||
|
||||
for key, modifiedValue := range modified {
|
||||
originalValue, ok := original[key]
|
||||
// value was added
|
||||
if !ok {
|
||||
if !ignoreAdditions {
|
||||
// Key was added, so add to patch
|
||||
if !ignoreChangesAndAdditions {
|
||||
patch[key] = modifiedValue
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if key == specialKey {
|
||||
if key == directiveMarker {
|
||||
originalString, ok := originalValue.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid value for special key: %s", specialKey)
|
||||
return nil, fmt.Errorf("invalid value for special key: %s", directiveMarker)
|
||||
}
|
||||
|
||||
modifiedString, ok := modifiedValue.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid value for special key: %s", specialKey)
|
||||
return nil, fmt.Errorf("invalid value for special key: %s", directiveMarker)
|
||||
}
|
||||
|
||||
if modifiedString != originalString {
|
||||
patch[directiveMarker] = modifiedValue
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if reflect.TypeOf(originalValue) != reflect.TypeOf(modifiedValue) {
|
||||
// Types have changed, so add to patch
|
||||
if !ignoreChangesAndAdditions {
|
||||
patch[key] = modifiedValue
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !ignoreChangesAndDeletions {
|
||||
// If types have changed, replace completely
|
||||
if reflect.TypeOf(originalValue) != reflect.TypeOf(modifiedValue) {
|
||||
patch[key] = modifiedValue
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Types are the same, compare values
|
||||
// Types are the same, so compare values
|
||||
switch originalValueTyped := originalValue.(type) {
|
||||
case map[string]interface{}:
|
||||
modifiedValueTyped := modifiedValue.(map[string]interface{})
|
||||
@ -124,7 +205,7 @@ func diffMaps(original, modified map[string]interface{}, t reflect.Type, ignoreA
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patchValue, err := diffMaps(originalValueTyped, modifiedValueTyped, fieldType, ignoreAdditions, ignoreChangesAndDeletions)
|
||||
patchValue, err := diffMaps(originalValueTyped, modifiedValueTyped, fieldType, ignoreChangesAndAdditions, ignoreDeletions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -141,8 +222,8 @@ func diffMaps(original, modified map[string]interface{}, t reflect.Type, ignoreA
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fieldPatchStrategy == "merge" {
|
||||
patchValue, err := diffLists(originalValueTyped, modifiedValueTyped, fieldType.Elem(), fieldPatchMergeKey, ignoreAdditions, ignoreChangesAndDeletions)
|
||||
if fieldPatchStrategy == mergeDirective {
|
||||
patchValue, err := diffLists(originalValueTyped, modifiedValueTyped, fieldType.Elem(), fieldPatchMergeKey, ignoreChangesAndAdditions, ignoreDeletions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -155,15 +236,16 @@ func diffMaps(original, modified map[string]interface{}, t reflect.Type, ignoreA
|
||||
}
|
||||
}
|
||||
|
||||
if !ignoreChangesAndDeletions {
|
||||
if !ignoreChangesAndAdditions {
|
||||
if !reflect.DeepEqual(originalValue, modifiedValue) {
|
||||
// Values are different, so add to patch
|
||||
patch[key] = modifiedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !ignoreChangesAndDeletions {
|
||||
// Now add all deleted values as nil
|
||||
if !ignoreDeletions {
|
||||
// Add nils for deleted values
|
||||
for key := range original {
|
||||
_, found := modified[key]
|
||||
if !found {
|
||||
@ -177,9 +259,9 @@ func diffMaps(original, modified map[string]interface{}, t reflect.Type, ignoreA
|
||||
|
||||
// Returns a (recursive) strategic merge patch that yields modified when applied to original,
|
||||
// for a pair of lists with merge semantics.
|
||||
func diffLists(original, modified []interface{}, t reflect.Type, mergeKey string, ignoreAdditions, ignoreChangesAndDeletions bool) ([]interface{}, error) {
|
||||
func diffLists(original, modified []interface{}, t reflect.Type, mergeKey string, ignoreChangesAndAdditions, ignoreDeletions bool) ([]interface{}, error) {
|
||||
if len(original) == 0 {
|
||||
if len(modified) == 0 || ignoreAdditions {
|
||||
if len(modified) == 0 || ignoreChangesAndAdditions {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -193,11 +275,10 @@ func diffLists(original, modified []interface{}, t reflect.Type, mergeKey string
|
||||
|
||||
var patch []interface{}
|
||||
|
||||
// If the elements are not maps...
|
||||
if elementType.Kind() == reflect.Map {
|
||||
patch, err = diffListsOfMaps(original, modified, t, mergeKey, ignoreAdditions, ignoreChangesAndDeletions)
|
||||
} else {
|
||||
patch, err = diffListsOfScalars(original, modified, ignoreAdditions)
|
||||
patch, err = diffListsOfMaps(original, modified, t, mergeKey, ignoreChangesAndAdditions, ignoreDeletions)
|
||||
} else if !ignoreChangesAndAdditions {
|
||||
patch, err = diffListsOfScalars(original, modified)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -209,7 +290,7 @@ func diffLists(original, modified []interface{}, t reflect.Type, mergeKey string
|
||||
|
||||
// Returns a (recursive) strategic merge patch that yields modified when applied to original,
|
||||
// for a pair of lists of scalars with merge semantics.
|
||||
func diffListsOfScalars(original, modified []interface{}, ignoreAdditions bool) ([]interface{}, error) {
|
||||
func diffListsOfScalars(original, modified []interface{}) ([]interface{}, error) {
|
||||
if len(modified) == 0 {
|
||||
// There is no need to check the length of original because there is no way to create
|
||||
// a patch that deletes a scalar from a list of scalars with merge semantics.
|
||||
@ -229,9 +310,7 @@ loopB:
|
||||
modifiedString := fmt.Sprintf("%v", modified[modifiedIndex])
|
||||
if originalString >= modifiedString {
|
||||
if originalString != modifiedString {
|
||||
if !ignoreAdditions {
|
||||
patch = append(patch, modified[modifiedIndex])
|
||||
}
|
||||
patch = append(patch, modified[modifiedIndex])
|
||||
}
|
||||
|
||||
continue loopB
|
||||
@ -243,11 +322,9 @@ loopB:
|
||||
break
|
||||
}
|
||||
|
||||
if !ignoreAdditions {
|
||||
// Add any remaining items found only in modified
|
||||
for ; modifiedIndex < len(modifiedScalars); modifiedIndex++ {
|
||||
patch = append(patch, modified[modifiedIndex])
|
||||
}
|
||||
// Add any remaining items found only in modified
|
||||
for ; modifiedIndex < len(modifiedScalars); modifiedIndex++ {
|
||||
patch = append(patch, modified[modifiedIndex])
|
||||
}
|
||||
|
||||
return patch, nil
|
||||
@ -258,7 +335,7 @@ var errBadArgTypeFmt = "expected a %s, but received a %t"
|
||||
|
||||
// Returns a (recursive) strategic merge patch that yields modified when applied to original,
|
||||
// for a pair of lists of maps with merge semantics.
|
||||
func diffListsOfMaps(original, modified []interface{}, t reflect.Type, mergeKey string, ignoreAdditions, ignoreChangesAndDeletions bool) ([]interface{}, error) {
|
||||
func diffListsOfMaps(original, modified []interface{}, t reflect.Type, mergeKey string, ignoreChangesAndAdditions, ignoreDeletions bool) ([]interface{}, error) {
|
||||
patch := make([]interface{}, 0)
|
||||
|
||||
originalSorted, err := sortMergeListsByNameArray(original, t, mergeKey, false)
|
||||
@ -301,7 +378,8 @@ loopB:
|
||||
modifiedString := fmt.Sprintf("%v", modifiedValue)
|
||||
if originalString >= modifiedString {
|
||||
if originalString == modifiedString {
|
||||
patchValue, err := diffMaps(originalMap, modifiedMap, t, ignoreAdditions, ignoreChangesAndDeletions)
|
||||
// Merge key values are equal, so recurse
|
||||
patchValue, err := diffMaps(originalMap, modifiedMap, t, ignoreChangesAndAdditions, ignoreDeletions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -311,22 +389,24 @@ loopB:
|
||||
patchValue[mergeKey] = modifiedValue
|
||||
patch = append(patch, patchValue)
|
||||
}
|
||||
} else if !ignoreAdditions {
|
||||
} else if !ignoreChangesAndAdditions {
|
||||
// Item was added, so add to patch
|
||||
patch = append(patch, modifiedMap)
|
||||
}
|
||||
|
||||
continue loopB
|
||||
}
|
||||
|
||||
if !ignoreChangesAndDeletions {
|
||||
patch = append(patch, map[string]interface{}{mergeKey: originalValue, specialKey: specialValue})
|
||||
if !ignoreDeletions {
|
||||
// Item was deleted, so add delete directive
|
||||
patch = append(patch, map[string]interface{}{mergeKey: originalValue, directiveMarker: deleteDirective})
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if !ignoreChangesAndDeletions {
|
||||
if !ignoreDeletions {
|
||||
// Delete any remaining items found only in original
|
||||
for ; originalIndex < len(originalSorted); originalIndex++ {
|
||||
originalMap, ok := originalSorted[originalIndex].(map[string]interface{})
|
||||
@ -339,11 +419,11 @@ loopB:
|
||||
return nil, fmt.Errorf(errNoMergeKeyFmt, originalMap, mergeKey)
|
||||
}
|
||||
|
||||
patch = append(patch, map[string]interface{}{mergeKey: originalValue, specialKey: specialValue})
|
||||
patch = append(patch, map[string]interface{}{mergeKey: originalValue, directiveMarker: deleteDirective})
|
||||
}
|
||||
}
|
||||
|
||||
if !ignoreAdditions {
|
||||
if !ignoreChangesAndAdditions {
|
||||
// Add any remaining items found only in modified
|
||||
for ; modifiedIndex < len(modifiedSorted); modifiedIndex++ {
|
||||
patch = append(patch, modified[modifiedIndex])
|
||||
@ -353,7 +433,6 @@ loopB:
|
||||
return patch, nil
|
||||
}
|
||||
|
||||
// StrategicMergePatchData applies a patch using strategic merge patch semantics.
|
||||
// Deprecated: StrategicMergePatchData is deprecated. Use the synonym StrategicMergePatch,
|
||||
// instead, which follows the naming convention of evanphx/json-patch.
|
||||
func StrategicMergePatchData(original, patch []byte, dataStruct interface{}) ([]byte, error) {
|
||||
@ -364,6 +443,14 @@ func StrategicMergePatchData(original, patch []byte, dataStruct interface{}) ([]
|
||||
// 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{}) ([]byte, error) {
|
||||
if original == nil {
|
||||
original = []byte{}
|
||||
}
|
||||
|
||||
if patch == nil {
|
||||
patch = []byte{}
|
||||
}
|
||||
|
||||
originalMap := map[string]interface{}{}
|
||||
err := json.Unmarshal(original, &originalMap)
|
||||
if err != nil {
|
||||
@ -390,6 +477,10 @@ func StrategicMergePatch(original, patch []byte, dataStruct interface{}) ([]byte
|
||||
}
|
||||
|
||||
func getTagStructType(dataStruct interface{}) (reflect.Type, error) {
|
||||
if dataStruct == nil {
|
||||
return nil, fmt.Errorf(errBadArgTypeFmt, "struct", "nil")
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(dataStruct)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
@ -408,21 +499,26 @@ var errBadPatchTypeFmt = "unknown patch type: %s in map: %v"
|
||||
// both the original map and the patch because getting a deep copy of a map in
|
||||
// golang is highly non-trivial.
|
||||
func mergeMap(original, patch map[string]interface{}, t reflect.Type) (map[string]interface{}, error) {
|
||||
// If the map contains "$patch: replace", don't merge it, just use the
|
||||
// patch map directly. Later on, can add a non-recursive replace that only
|
||||
// affects the map that the $patch is in.
|
||||
if v, ok := patch[specialKey]; ok {
|
||||
if v == "replace" {
|
||||
delete(patch, specialKey)
|
||||
if v, ok := patch[directiveMarker]; ok {
|
||||
if v == replaceDirective {
|
||||
// If the patch contains "$patch: replace", don't merge it, just use the
|
||||
// patch directly. Later on, we can add a single level replace that only
|
||||
// affects the map that the $patch is in.
|
||||
delete(patch, directiveMarker)
|
||||
return patch, nil
|
||||
}
|
||||
|
||||
if v == deleteDirective {
|
||||
// If the patch contains "$patch: delete", don't merge it, just return
|
||||
// an empty map.
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(errBadPatchTypeFmt, v, patch)
|
||||
}
|
||||
|
||||
// nil is an accepted value for original to simplify logic in other places.
|
||||
// If original is nil, create a map so if patch requires us to modify the
|
||||
// map, it'll work.
|
||||
// If original is nil, replace it with an empty map and then apply the patch.
|
||||
if original == nil {
|
||||
original = map[string]interface{}{}
|
||||
}
|
||||
@ -461,7 +557,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type) (map[strin
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if originalType.Kind() == reflect.Map && fieldPatchStrategy != "replace" {
|
||||
if originalType.Kind() == reflect.Map && fieldPatchStrategy != replaceDirective {
|
||||
typedOriginal := original[k].(map[string]interface{})
|
||||
typedPatch := patchV.(map[string]interface{})
|
||||
var err error
|
||||
@ -473,7 +569,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type) (map[strin
|
||||
continue
|
||||
}
|
||||
|
||||
if originalType.Kind() == reflect.Slice && fieldPatchStrategy == "merge" {
|
||||
if originalType.Kind() == reflect.Slice && fieldPatchStrategy == mergeDirective {
|
||||
elemType := fieldType.Elem()
|
||||
typedOriginal := original[k].([]interface{})
|
||||
typedPatch := patchV.([]interface{})
|
||||
@ -527,9 +623,9 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s
|
||||
replace := false
|
||||
for _, v := range patch {
|
||||
typedV := v.(map[string]interface{})
|
||||
patchType, ok := typedV[specialKey]
|
||||
patchType, ok := typedV[directiveMarker]
|
||||
if ok {
|
||||
if patchType == specialValue {
|
||||
if patchType == deleteDirective {
|
||||
mergeValue, ok := typedV[mergeKey]
|
||||
if ok {
|
||||
_, originalKey, found, err := findMapInSliceBasedOnKeyValue(original, mergeKey, mergeValue)
|
||||
@ -544,10 +640,10 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s
|
||||
} else {
|
||||
return nil, fmt.Errorf("delete patch type with no merge key defined")
|
||||
}
|
||||
} else if patchType == "replace" {
|
||||
} else if patchType == replaceDirective {
|
||||
replace = true
|
||||
// Continue iterating through the array to prune any other $patch elements.
|
||||
} else if patchType == "merge" {
|
||||
} else if patchType == mergeDirective {
|
||||
return nil, fmt.Errorf("merging lists cannot yet be specified in the patch")
|
||||
} else {
|
||||
return nil, fmt.Errorf(errBadPatchTypeFmt, patchType, typedV)
|
||||
@ -636,7 +732,7 @@ func sortMergeListsByName(mapJSON []byte, dataStruct interface{}) ([]byte, error
|
||||
func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[string]interface{}, error) {
|
||||
newS := map[string]interface{}{}
|
||||
for k, v := range s {
|
||||
if k != specialKey {
|
||||
if k != directiveMarker {
|
||||
fieldType, fieldPatchStrategy, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -650,7 +746,7 @@ func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[stri
|
||||
return nil, err
|
||||
}
|
||||
} else if typedV, ok := v.([]interface{}); ok {
|
||||
if fieldPatchStrategy == "merge" {
|
||||
if fieldPatchStrategy == mergeDirective {
|
||||
var err error
|
||||
v, err = sortMergeListsByNameArray(typedV, fieldType.Elem(), fieldPatchMergeKey, true)
|
||||
if err != nil {
|
||||
@ -829,10 +925,8 @@ func sliceElementType(slices ...[]interface{}) (reflect.Type, error) {
|
||||
}
|
||||
|
||||
// HasConflicts returns true if the left and right JSON interface objects overlap with
|
||||
// different values in any key. The code will panic if an unrecognized type is passed
|
||||
// (anything not returned by a JSON decode). All keys are required to be strings.
|
||||
// Since patches of the same Type have congruent keys, this is valid for multiple patch
|
||||
// types.
|
||||
// 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.
|
||||
func HasConflicts(left, right interface{}) (bool, error) {
|
||||
switch typedLeft := left.(type) {
|
||||
case map[string]interface{}:
|
||||
@ -868,3 +962,69 @@ func HasConflicts(left, right interface{}) (bool, error) {
|
||||
return true, fmt.Errorf("unknown type: %v", reflect.TypeOf(left))
|
||||
}
|
||||
}
|
||||
|
||||
// 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 force is false and there are conflicts between the modified and current
|
||||
// configurations.
|
||||
func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct interface{}, force bool, fns ...PreconditionFunc) ([]byte, error) {
|
||||
originalMap := map[string]interface{}{}
|
||||
if len(original) > 0 {
|
||||
if err := json.Unmarshal(original, &originalMap); err != nil {
|
||||
return nil, errBadJSONDoc
|
||||
}
|
||||
}
|
||||
|
||||
modifiedMap := map[string]interface{}{}
|
||||
if len(modified) > 0 {
|
||||
if err := json.Unmarshal(modified, &modifiedMap); err != nil {
|
||||
return nil, errBadJSONDoc
|
||||
}
|
||||
}
|
||||
|
||||
currentMap := map[string]interface{}{}
|
||||
if len(current) > 0 {
|
||||
if err := json.Unmarshal(current, ¤tMap); err != nil {
|
||||
return nil, errBadJSONDoc
|
||||
}
|
||||
}
|
||||
|
||||
t, err := getTagStructType(dataStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The patch is the difference from current to modified without deletions, plus deletions
|
||||
// from original to modified. To find it, we compute deletions, which are the deletions from
|
||||
// original to modified, and delta, which is the difference from current to modified without
|
||||
// deletions, and then apply delta to deletions as a patch, which should be strictly additive.
|
||||
deltaMap, err := diffMaps(currentMap, modifiedMap, t, false, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deletionsMap, err := diffMaps(originalMap, modifiedMap, t, true, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patchMap, err := mergeMap(deletionsMap, deltaMap, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply the preconditions to the patch, and return an error if any of them fail.
|
||||
for _, fn := range fns {
|
||||
if !fn(patchMap) {
|
||||
return nil, newErrPreconditionFailed(patchMap)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jackgr): If force is false, and the patch contains any keys that are also in current,
|
||||
// then return a conflict error.
|
||||
|
||||
return json.Marshal(patchMap)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user