mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Add method to apply strategic merge patch.
This commit is contained in:
		@@ -35,28 +35,101 @@ 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 {
 | 
			
		||||
	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 {
 | 
			
		||||
	if len(modified) > 0 {
 | 
			
		||||
		if err := json.Unmarshal(modified, &modifiedMap); err != nil {
 | 
			
		||||
			return nil, errBadJSONDoc
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t, err := getTagStructType(dataStruct)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -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[key] = modifiedValue
 | 
			
		||||
				patch[directiveMarker] = modifiedValue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !ignoreChangesAndDeletions {
 | 
			
		||||
			// If types have changed, replace completely
 | 
			
		||||
		if reflect.TypeOf(originalValue) != reflect.TypeOf(modifiedValue) {
 | 
			
		||||
			// Types have changed, so add to patch
 | 
			
		||||
			if !ignoreChangesAndAdditions {
 | 
			
		||||
				patch[key] = modifiedValue
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		// Types are the same, compare values
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 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,10 +310,8 @@ loopB:
 | 
			
		||||
			modifiedString := fmt.Sprintf("%v", modified[modifiedIndex])
 | 
			
		||||
			if originalString >= modifiedString {
 | 
			
		||||
				if originalString != modifiedString {
 | 
			
		||||
					if !ignoreAdditions {
 | 
			
		||||
					patch = append(patch, modified[modifiedIndex])
 | 
			
		||||
				}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				continue loopB
 | 
			
		||||
			}
 | 
			
		||||
@@ -243,12 +322,10 @@ loopB:
 | 
			
		||||
		break
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ignoreAdditions {
 | 
			
		||||
	// 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
 | 
			
		||||
	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.
 | 
			
		||||
	if v, ok := patch[specialKey]; ok {
 | 
			
		||||
		if v == "replace" {
 | 
			
		||||
			delete(patch, specialKey)
 | 
			
		||||
			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
											
										
									
								
							
		Reference in New Issue
	
	Block a user