mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 17:30:00 +00:00
CreateThreeWayMergePatch should propagate explicit null fields
This commit is contained in:
parent
4bdcc03c0b
commit
31c6f49846
@ -548,7 +548,7 @@ func StrategicMergeMapPatch(original, patch JSONMap, dataStruct interface{}) (JS
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return mergeMap(original, patch, t, true)
|
return mergeMap(original, patch, t, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTagStructType(dataStruct interface{}) (reflect.Type, error) {
|
func getTagStructType(dataStruct interface{}) (reflect.Type, error) {
|
||||||
@ -574,7 +574,10 @@ 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
|
// both the original map and the patch because getting a deep copy of a map in
|
||||||
// golang is highly non-trivial.
|
// golang is highly non-trivial.
|
||||||
// flag mergeDeleteList controls if using the parallel list to delete or keeping the list.
|
// flag mergeDeleteList controls if using the parallel list to delete or keeping the list.
|
||||||
func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeDeleteList bool) (map[string]interface{}, error) {
|
// 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
|
||||||
|
// ignoreUnmatchedNulls == false.
|
||||||
|
func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeDeleteList, ignoreUnmatchedNulls bool) (map[string]interface{}, error) {
|
||||||
if v, ok := patch[directiveMarker]; ok {
|
if v, ok := patch[directiveMarker]; ok {
|
||||||
if v == replaceDirective {
|
if v == replaceDirective {
|
||||||
// If the patch contains "$patch: replace", don't merge it, just use the
|
// If the patch contains "$patch: replace", don't merge it, just use the
|
||||||
@ -619,13 +622,17 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeDelet
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the value of this key is null, delete the key if it exists in the
|
// If the value of this key is null, delete the key if it exists in the
|
||||||
// original. Otherwise, skip it.
|
// original. Otherwise, check if we want to preserve it or skip it.
|
||||||
|
// Preserving the null value is useful when we want to send an explicit
|
||||||
|
// delete to the API server.
|
||||||
if patchV == nil {
|
if patchV == nil {
|
||||||
if _, ok := original[k]; ok {
|
if _, ok := original[k]; ok {
|
||||||
delete(original, k)
|
delete(original, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
if ignoreUnmatchedNulls {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := original[k]
|
_, ok := original[k]
|
||||||
@ -654,7 +661,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeDelet
|
|||||||
typedOriginal := original[k].(map[string]interface{})
|
typedOriginal := original[k].(map[string]interface{})
|
||||||
typedPatch := patchV.(map[string]interface{})
|
typedPatch := patchV.(map[string]interface{})
|
||||||
var err error
|
var err error
|
||||||
original[k], err = mergeMap(typedOriginal, typedPatch, fieldType, mergeDeleteList)
|
original[k], err = mergeMap(typedOriginal, typedPatch, fieldType, mergeDeleteList, ignoreUnmatchedNulls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -667,7 +674,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeDelet
|
|||||||
typedOriginal := original[k].([]interface{})
|
typedOriginal := original[k].([]interface{})
|
||||||
typedPatch := patchV.([]interface{})
|
typedPatch := patchV.([]interface{})
|
||||||
var err error
|
var err error
|
||||||
original[k], err = mergeSlice(typedOriginal, typedPatch, elemType, fieldPatchMergeKey, mergeDeleteList, isDeleteList)
|
original[k], err = mergeSlice(typedOriginal, typedPatch, elemType, fieldPatchMergeKey, mergeDeleteList, isDeleteList, ignoreUnmatchedNulls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -688,7 +695,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeDelet
|
|||||||
// Merge two slices together. Note: This may modify both the original slice and
|
// 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
|
// the patch because getting a deep copy of a slice in golang is highly
|
||||||
// non-trivial.
|
// non-trivial.
|
||||||
func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey string, mergeDeleteList, isDeleteList bool) ([]interface{}, error) {
|
func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey string, mergeDeleteList, isDeleteList, ignoreUnmatchedNulls bool) ([]interface{}, error) {
|
||||||
if len(original) == 0 && len(patch) == 0 {
|
if len(original) == 0 && len(patch) == 0 {
|
||||||
return original, nil
|
return original, nil
|
||||||
}
|
}
|
||||||
@ -779,7 +786,7 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s
|
|||||||
var mergedMaps interface{}
|
var mergedMaps interface{}
|
||||||
var err error
|
var err error
|
||||||
// Merge into original.
|
// Merge into original.
|
||||||
mergedMaps, err = mergeMap(originalMap, typedV, elemType, mergeDeleteList)
|
mergedMaps, err = mergeMap(originalMap, typedV, elemType, mergeDeleteList, ignoreUnmatchedNulls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1282,7 +1289,8 @@ func mapsOfMapsHaveConflicts(typedLeft, typedRight map[string]interface{}, struc
|
|||||||
// configurations. Conflicts are defined as keys changed differently from original to modified
|
// 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
|
// 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
|
// in a way that is different from how it is changed in current (e.g., deleting it, changing its
|
||||||
// value).
|
// value). We also propagate values fields that do not exist in original but are explicitly
|
||||||
|
// defined in modified.
|
||||||
func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct interface{}, overwrite bool, fns ...PreconditionFunc) ([]byte, error) {
|
func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct interface{}, overwrite bool, fns ...PreconditionFunc) ([]byte, error) {
|
||||||
originalMap := map[string]interface{}{}
|
originalMap := map[string]interface{}{}
|
||||||
if len(original) > 0 {
|
if len(original) > 0 {
|
||||||
@ -1324,7 +1332,7 @@ func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct int
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
patchMap, err := mergeMap(deletionsMap, deltaMap, t, false)
|
patchMap, err := mergeMap(deletionsMap, deltaMap, t, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -532,7 +532,7 @@ testCases:
|
|||||||
threeWay:
|
threeWay:
|
||||||
name: null
|
name: null
|
||||||
value: null
|
value: null
|
||||||
result:
|
result:
|
||||||
other: a
|
other: a
|
||||||
- description: delete all fields from map with conflict
|
- description: delete all fields from map with conflict
|
||||||
original:
|
original:
|
||||||
@ -549,7 +549,7 @@ testCases:
|
|||||||
threeWay:
|
threeWay:
|
||||||
name: null
|
name: null
|
||||||
value: null
|
value: null
|
||||||
result:
|
result:
|
||||||
other: a
|
other: a
|
||||||
- description: add field and delete all fields from map
|
- description: add field and delete all fields from map
|
||||||
original:
|
original:
|
||||||
@ -677,12 +677,12 @@ testCases:
|
|||||||
mergingList:
|
mergingList:
|
||||||
- name: 3
|
- name: 3
|
||||||
value: 3
|
value: 3
|
||||||
- name: 4
|
- name: 4
|
||||||
value: 4
|
value: 4
|
||||||
modified:
|
modified:
|
||||||
mergingList:
|
mergingList:
|
||||||
- name: 4
|
- name: 4
|
||||||
value: 4
|
value: 4
|
||||||
- name: 1
|
- name: 1
|
||||||
- name: 2
|
- name: 2
|
||||||
value: 2
|
value: 2
|
||||||
@ -699,8 +699,8 @@ testCases:
|
|||||||
mergingList:
|
mergingList:
|
||||||
- name: 3
|
- name: 3
|
||||||
value: 3
|
value: 3
|
||||||
- name: 4
|
- name: 4
|
||||||
value: 4
|
value: 4
|
||||||
result:
|
result:
|
||||||
mergingList:
|
mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
@ -710,8 +710,8 @@ testCases:
|
|||||||
other: b
|
other: b
|
||||||
- name: 3
|
- name: 3
|
||||||
value: 3
|
value: 3
|
||||||
- name: 4
|
- name: 4
|
||||||
value: 4
|
value: 4
|
||||||
- description: merge lists of maps with conflict
|
- description: merge lists of maps with conflict
|
||||||
original:
|
original:
|
||||||
mergingList:
|
mergingList:
|
||||||
@ -1817,6 +1817,27 @@ testCases:
|
|||||||
other: b
|
other: b
|
||||||
- name: 2
|
- name: 2
|
||||||
other: b
|
other: b
|
||||||
|
- description: defined null values should propagate overwrite current fields (with conflict) (ignore two-way application)
|
||||||
|
original:
|
||||||
|
name: 2
|
||||||
|
twoWay:
|
||||||
|
name: 1
|
||||||
|
value: 1
|
||||||
|
other: null
|
||||||
|
modified:
|
||||||
|
name: 1
|
||||||
|
value: 1
|
||||||
|
other: null
|
||||||
|
current:
|
||||||
|
name: a
|
||||||
|
other: a
|
||||||
|
threeWay:
|
||||||
|
name: 1
|
||||||
|
value: 1
|
||||||
|
other: null
|
||||||
|
result:
|
||||||
|
name: 1
|
||||||
|
value: 1
|
||||||
`)
|
`)
|
||||||
|
|
||||||
var strategicMergePatchRawTestCases = []StrategicMergePatchRawTestCase{
|
var strategicMergePatchRawTestCases = []StrategicMergePatchRawTestCase{
|
||||||
@ -1992,7 +2013,9 @@ func testTwoWayPatch(t *testing.T, c StrategicMergePatchTestCase) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testPatchCreation(t, expected, actual, c.Description)
|
testPatchCreation(t, expected, actual, c.Description)
|
||||||
testPatchApplication(t, original, actual, modified, c.Description)
|
if !strings.Contains(c.Description, "ignore two-way application") {
|
||||||
|
testPatchApplication(t, original, actual, modified, c.Description)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase) {
|
func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase) {
|
||||||
|
Loading…
Reference in New Issue
Block a user