mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 04:06:03 +00:00
support generic 3-way json merge patch
This commit is contained in:
parent
9805b0bdfb
commit
8aa16e09d4
144
staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch.go
Normal file
144
staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch.go
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package jsonmergepatch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
"github.com/evanphx/json-patch"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
)
|
||||
|
||||
// Create a 3-way merge patch based-on JSON merge patch.
|
||||
// Calculate addition-and-change patch between current and modified.
|
||||
// Calculate deletion patch between original and modified.
|
||||
func CreateThreeWayJSONMergePatch(original, modified, current []byte, fns ...strategicpatch.PreconditionFunc) ([]byte, error) {
|
||||
if len(original) == 0 {
|
||||
original = []byte(`{}`)
|
||||
}
|
||||
if len(modified) == 0 {
|
||||
modified = []byte(`{}`)
|
||||
}
|
||||
if len(current) == 0 {
|
||||
current = []byte(`{}`)
|
||||
}
|
||||
|
||||
addAndChangePatch, err := jsonpatch.CreateMergePatch(current, modified)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Only keep addition and changes
|
||||
addAndChangePatch, addAndChangePatchObj, err := keepOrDeleteNullInJsonPatch(addAndChangePatch, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deletePatch, err := jsonpatch.CreateMergePatch(original, modified)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Only keep deletion
|
||||
deletePatch, deletePatchObj, err := keepOrDeleteNullInJsonPatch(deletePatch, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hasConflicts, err := strategicpatch.HasConflicts(addAndChangePatchObj, deletePatchObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hasConflicts {
|
||||
return nil, strategicpatch.NewErrConflict(strategicpatch.ToYAMLOrError(addAndChangePatchObj), strategicpatch.ToYAMLOrError(deletePatchObj))
|
||||
}
|
||||
patch, err := jsonpatch.MergePatch(deletePatch, addAndChangePatch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var patchMap map[string]interface{}
|
||||
err = json.Unmarshal(patch, &patchMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to unmarshal patch for precondition check: %s", patch)
|
||||
}
|
||||
meetPreconditions, err := meetPreconditions(patchMap, fns...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !meetPreconditions {
|
||||
return nil, strategicpatch.NewErrPreconditionFailed(patchMap)
|
||||
}
|
||||
|
||||
return patch, nil
|
||||
}
|
||||
|
||||
// keepOrDeleteNullInJsonPatch takes a json-encoded byte array and a boolean.
|
||||
// It returns a filtered object and its corresponding json-encoded byte array.
|
||||
// It is a wrapper of func keepOrDeleteNullInObj
|
||||
func keepOrDeleteNullInJsonPatch(patch []byte, keepNull bool) ([]byte, map[string]interface{}, error) {
|
||||
var patchMap map[string]interface{}
|
||||
err := json.Unmarshal(patch, &patchMap)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
filteredMap, err := keepOrDeleteNullInObj(patchMap, keepNull)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
o, err := json.Marshal(filteredMap)
|
||||
return o, filteredMap, err
|
||||
}
|
||||
|
||||
// keepOrDeleteNullInObj will keep only the null value and delete all the others,
|
||||
// if keepNull is true. Otherwise, it will delete all the null value and keep the others.
|
||||
func keepOrDeleteNullInObj(m map[string]interface{}, keepNull bool) (map[string]interface{}, error) {
|
||||
filteredMap := make(map[string]interface{})
|
||||
var err error
|
||||
for key, val := range m {
|
||||
switch {
|
||||
case keepNull && val == nil:
|
||||
filteredMap[key] = nil
|
||||
case val != nil:
|
||||
switch typedVal := val.(type) {
|
||||
case map[string]interface{}:
|
||||
filteredMap[key], err = keepOrDeleteNullInObj(typedVal, keepNull)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case []interface{}, string, float64, bool, int, int64, nil:
|
||||
// Lists are always replaced in Json, no need to check each entry in the list.
|
||||
if !keepNull {
|
||||
filteredMap[key] = val
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown type: %v", reflect.TypeOf(typedVal))
|
||||
}
|
||||
}
|
||||
}
|
||||
return filteredMap, nil
|
||||
}
|
||||
|
||||
func meetPreconditions(patchObj map[string]interface{}, fns ...strategicpatch.PreconditionFunc) (bool, error) {
|
||||
// Apply the preconditions to the patch, and return an error if any of them fail.
|
||||
for _, fn := range fns {
|
||||
if !fn(patchObj) {
|
||||
return false, fmt.Errorf("precondition failed for: %v", patchObj)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
@ -0,0 +1,664 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package jsonmergepatch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/evanphx/json-patch"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
type FilterNullTestCases struct {
|
||||
TestCases []FilterNullTestCase
|
||||
}
|
||||
|
||||
type FilterNullTestCase struct {
|
||||
Description string
|
||||
OriginalObj map[string]interface{}
|
||||
ExpectedWithNull map[string]interface{}
|
||||
ExpectedWithoutNull map[string]interface{}
|
||||
}
|
||||
|
||||
var filterNullTestCaseData = []byte(`
|
||||
testCases:
|
||||
- description: nil original
|
||||
originalObj: {}
|
||||
expectedWithNull: {}
|
||||
expectedWithoutNull: {}
|
||||
- description: simple map
|
||||
originalObj:
|
||||
nilKey: null
|
||||
nonNilKey: foo
|
||||
expectedWithNull:
|
||||
nilKey: null
|
||||
expectedWithoutNull:
|
||||
nonNilKey: foo
|
||||
- description: simple map with all nil values
|
||||
originalObj:
|
||||
nilKey1: null
|
||||
nilKey2: null
|
||||
expectedWithNull:
|
||||
nilKey1: null
|
||||
nilKey2: null
|
||||
expectedWithoutNull: {}
|
||||
- description: simple map with all non-nil values
|
||||
originalObj:
|
||||
nonNilKey: foo
|
||||
nonNilKey: bar
|
||||
expectedWithNull: {}
|
||||
expectedWithoutNull:
|
||||
nonNilKey: foo
|
||||
nonNilKey: bar
|
||||
- description: nested map
|
||||
originalObj:
|
||||
mapKey:
|
||||
nilKey: null
|
||||
nonNilKey: foo
|
||||
expectedWithNull:
|
||||
mapKey:
|
||||
nilKey: null
|
||||
expectedWithoutNull:
|
||||
mapKey:
|
||||
nonNilKey: foo
|
||||
- description: nested map that all subkeys are nil
|
||||
originalObj:
|
||||
mapKey:
|
||||
nilKey1: null
|
||||
nilKey2: null
|
||||
expectedWithNull:
|
||||
mapKey:
|
||||
nilKey1: null
|
||||
nilKey2: null
|
||||
expectedWithoutNull:
|
||||
mapKey: {}
|
||||
- description: nested map that all subkeys are non-nil
|
||||
originalObj:
|
||||
mapKey:
|
||||
nonNilKey: foo
|
||||
nonNilKey: bar
|
||||
expectedWithNull:
|
||||
mapKey: {}
|
||||
expectedWithoutNull:
|
||||
mapKey:
|
||||
nonNilKey: foo
|
||||
nonNilKey: bar
|
||||
- description: empty list
|
||||
originalObj:
|
||||
listKey: []
|
||||
expectedWithNull: {}
|
||||
expectedWithoutNull:
|
||||
listKey: []
|
||||
- description: list of primitives
|
||||
originalObj:
|
||||
listKey:
|
||||
- 1
|
||||
- 2
|
||||
expectedWithNull: {}
|
||||
expectedWithoutNull:
|
||||
listKey:
|
||||
- 1
|
||||
- 2
|
||||
- description: list of maps
|
||||
originalObj:
|
||||
listKey:
|
||||
- k1: v1
|
||||
- k2: null
|
||||
- k3: v3
|
||||
k4: null
|
||||
expectedWithNull: {}
|
||||
expectedWithoutNull:
|
||||
listKey:
|
||||
- k1: v1
|
||||
- k2: null
|
||||
- k3: v3
|
||||
k4: null
|
||||
- description: list of different types
|
||||
originalObj:
|
||||
listKey:
|
||||
- k1: v1
|
||||
- k2: null
|
||||
- v3
|
||||
expectedWithNull: {}
|
||||
expectedWithoutNull:
|
||||
listKey:
|
||||
- k1: v1
|
||||
- k2: null
|
||||
- v3
|
||||
`)
|
||||
|
||||
func TestKeepOrDeleteNullInObj(t *testing.T) {
|
||||
tc := FilterNullTestCases{}
|
||||
err := yaml.Unmarshal(filterNullTestCaseData, &tc)
|
||||
if err != nil {
|
||||
t.Fatalf("can't unmarshal test cases: %s\n", err)
|
||||
}
|
||||
|
||||
for _, test := range tc.TestCases {
|
||||
resultWithNull, err := keepOrDeleteNullInObj(test.OriginalObj, true)
|
||||
if err != nil {
|
||||
t.Errorf("Failed in test case %q when trying to keep null values: %s", test.Description, err)
|
||||
}
|
||||
if !reflect.DeepEqual(test.ExpectedWithNull, resultWithNull) {
|
||||
t.Errorf("Failed in test case %q when trying to keep null values:\nexpected expectedWithNull:\n%+v\nbut got:\n%+v\n", test.Description, test.ExpectedWithNull, resultWithNull)
|
||||
}
|
||||
|
||||
resultWithoutNull, err := keepOrDeleteNullInObj(test.OriginalObj, false)
|
||||
if err != nil {
|
||||
t.Errorf("Failed in test case %q when trying to keep non-null values: %s", test.Description, err)
|
||||
}
|
||||
if !reflect.DeepEqual(test.ExpectedWithoutNull, resultWithoutNull) {
|
||||
t.Errorf("Failed in test case %q when trying to keep non-null values:\n expected expectedWithoutNull:\n%+v\nbut got:\n%+v\n", test.Description, test.ExpectedWithoutNull, resultWithoutNull)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type JSONMergePatchTestCases struct {
|
||||
TestCases []JSONMergePatchTestCase
|
||||
}
|
||||
|
||||
type JSONMergePatchTestCase struct {
|
||||
Description string
|
||||
JSONMergePatchTestCaseData
|
||||
}
|
||||
|
||||
type JSONMergePatchTestCaseData struct {
|
||||
// Original is the original object (last-applied config in annotation)
|
||||
Original map[string]interface{}
|
||||
// Modified is the modified object (new config we want)
|
||||
Modified map[string]interface{}
|
||||
// Current is the current object (live config in the server)
|
||||
Current map[string]interface{}
|
||||
// ThreeWay is the expected three-way merge patch
|
||||
ThreeWay map[string]interface{}
|
||||
// Result is the expected object after applying the three-way patch on current object.
|
||||
Result map[string]interface{}
|
||||
}
|
||||
|
||||
var createJSONMergePatchTestCaseData = []byte(`
|
||||
testCases:
|
||||
- description: nil original
|
||||
modified:
|
||||
name: 1
|
||||
value: 1
|
||||
current:
|
||||
name: 1
|
||||
other: a
|
||||
threeWay:
|
||||
value: 1
|
||||
result:
|
||||
name: 1
|
||||
value: 1
|
||||
other: a
|
||||
- description: nil patch
|
||||
original:
|
||||
name: 1
|
||||
modified:
|
||||
name: 1
|
||||
current:
|
||||
name: 1
|
||||
threeWay:
|
||||
{}
|
||||
result:
|
||||
name: 1
|
||||
- description: add field to map
|
||||
original:
|
||||
name: 1
|
||||
modified:
|
||||
name: 1
|
||||
value: 1
|
||||
current:
|
||||
name: 1
|
||||
other: a
|
||||
threeWay:
|
||||
value: 1
|
||||
result:
|
||||
name: 1
|
||||
value: 1
|
||||
other: a
|
||||
- description: add field to map with conflict
|
||||
original:
|
||||
name: 1
|
||||
modified:
|
||||
name: 1
|
||||
value: 1
|
||||
current:
|
||||
name: a
|
||||
other: a
|
||||
threeWay:
|
||||
name: 1
|
||||
value: 1
|
||||
result:
|
||||
name: 1
|
||||
value: 1
|
||||
other: a
|
||||
- description: add field and delete field from map
|
||||
original:
|
||||
name: 1
|
||||
modified:
|
||||
value: 1
|
||||
current:
|
||||
name: 1
|
||||
other: a
|
||||
threeWay:
|
||||
name: null
|
||||
value: 1
|
||||
result:
|
||||
value: 1
|
||||
other: a
|
||||
- description: add field and delete field from map with conflict
|
||||
original:
|
||||
name: 1
|
||||
modified:
|
||||
value: 1
|
||||
current:
|
||||
name: a
|
||||
other: a
|
||||
threeWay:
|
||||
name: null
|
||||
value: 1
|
||||
result:
|
||||
value: 1
|
||||
other: a
|
||||
- description: delete field from nested map
|
||||
original:
|
||||
simpleMap:
|
||||
key1: 1
|
||||
key2: 1
|
||||
modified:
|
||||
simpleMap:
|
||||
key1: 1
|
||||
current:
|
||||
simpleMap:
|
||||
key1: 1
|
||||
key2: 1
|
||||
other: a
|
||||
threeWay:
|
||||
simpleMap:
|
||||
key2: null
|
||||
result:
|
||||
simpleMap:
|
||||
key1: 1
|
||||
other: a
|
||||
- description: delete field from nested map with conflict
|
||||
original:
|
||||
simpleMap:
|
||||
key1: 1
|
||||
key2: 1
|
||||
modified:
|
||||
simpleMap:
|
||||
key1: 1
|
||||
current:
|
||||
simpleMap:
|
||||
key1: a
|
||||
key2: 1
|
||||
other: a
|
||||
threeWay:
|
||||
simpleMap:
|
||||
key1: 1
|
||||
key2: null
|
||||
result:
|
||||
simpleMap:
|
||||
key1: 1
|
||||
other: a
|
||||
- description: delete all fields from map
|
||||
original:
|
||||
name: 1
|
||||
value: 1
|
||||
modified: {}
|
||||
current:
|
||||
name: 1
|
||||
value: 1
|
||||
other: a
|
||||
threeWay:
|
||||
name: null
|
||||
value: null
|
||||
result:
|
||||
other: a
|
||||
- description: delete all fields from map with conflict
|
||||
original:
|
||||
name: 1
|
||||
value: 1
|
||||
modified: {}
|
||||
current:
|
||||
name: 1
|
||||
value: a
|
||||
other: a
|
||||
threeWay:
|
||||
name: null
|
||||
value: null
|
||||
result:
|
||||
other: a
|
||||
- description: add field and delete all fields from map
|
||||
original:
|
||||
name: 1
|
||||
value: 1
|
||||
modified:
|
||||
other: a
|
||||
current:
|
||||
name: 1
|
||||
value: 1
|
||||
other: a
|
||||
threeWay:
|
||||
name: null
|
||||
value: null
|
||||
result:
|
||||
other: a
|
||||
- description: add field and delete all fields from map with conflict
|
||||
original:
|
||||
name: 1
|
||||
value: 1
|
||||
modified:
|
||||
other: a
|
||||
current:
|
||||
name: 1
|
||||
value: 1
|
||||
other: b
|
||||
threeWay:
|
||||
name: null
|
||||
value: null
|
||||
other: a
|
||||
result:
|
||||
other: a
|
||||
- description: replace list of scalars
|
||||
original:
|
||||
intList:
|
||||
- 1
|
||||
- 2
|
||||
modified:
|
||||
intList:
|
||||
- 2
|
||||
- 3
|
||||
current:
|
||||
intList:
|
||||
- 1
|
||||
- 2
|
||||
threeWay:
|
||||
intList:
|
||||
- 2
|
||||
- 3
|
||||
result:
|
||||
intList:
|
||||
- 2
|
||||
- 3
|
||||
- description: replace list of scalars with conflict
|
||||
original:
|
||||
intList:
|
||||
- 1
|
||||
- 2
|
||||
modified:
|
||||
intList:
|
||||
- 2
|
||||
- 3
|
||||
current:
|
||||
intList:
|
||||
- 1
|
||||
- 4
|
||||
threeWay:
|
||||
intList:
|
||||
- 2
|
||||
- 3
|
||||
result:
|
||||
intList:
|
||||
- 2
|
||||
- 3
|
||||
- description: patch with different scalar type
|
||||
original:
|
||||
foo: 1
|
||||
modified:
|
||||
foo: true
|
||||
current:
|
||||
foo: 1
|
||||
bar: 2
|
||||
threeWay:
|
||||
foo: true
|
||||
result:
|
||||
foo: true
|
||||
bar: 2
|
||||
- description: patch from scalar to list
|
||||
original:
|
||||
foo: 0
|
||||
modified:
|
||||
foo:
|
||||
- 1
|
||||
- 2
|
||||
current:
|
||||
foo: 0
|
||||
bar: 2
|
||||
threeWay:
|
||||
foo:
|
||||
- 1
|
||||
- 2
|
||||
result:
|
||||
foo:
|
||||
- 1
|
||||
- 2
|
||||
bar: 2
|
||||
- description: patch from list to scalar
|
||||
original:
|
||||
foo:
|
||||
- 1
|
||||
- 2
|
||||
modified:
|
||||
foo: 0
|
||||
current:
|
||||
foo:
|
||||
- 1
|
||||
- 2
|
||||
bar: 2
|
||||
threeWay:
|
||||
foo: 0
|
||||
result:
|
||||
foo: 0
|
||||
bar: 2
|
||||
- description: patch from scalar to map
|
||||
original:
|
||||
foo: 0
|
||||
modified:
|
||||
foo:
|
||||
baz: 1
|
||||
current:
|
||||
foo: 0
|
||||
bar: 2
|
||||
threeWay:
|
||||
foo:
|
||||
baz: 1
|
||||
result:
|
||||
foo:
|
||||
baz: 1
|
||||
bar: 2
|
||||
- description: patch from map to scalar
|
||||
original:
|
||||
foo:
|
||||
baz: 1
|
||||
modified:
|
||||
foo: 0
|
||||
current:
|
||||
foo:
|
||||
baz: 1
|
||||
bar: 2
|
||||
threeWay:
|
||||
foo: 0
|
||||
result:
|
||||
foo: 0
|
||||
bar: 2
|
||||
- description: patch from map to list
|
||||
original:
|
||||
foo:
|
||||
baz: 1
|
||||
modified:
|
||||
foo:
|
||||
- 1
|
||||
- 2
|
||||
current:
|
||||
foo:
|
||||
baz: 1
|
||||
bar: 2
|
||||
threeWay:
|
||||
foo:
|
||||
- 1
|
||||
- 2
|
||||
result:
|
||||
foo:
|
||||
- 1
|
||||
- 2
|
||||
bar: 2
|
||||
- description: patch from list to map
|
||||
original:
|
||||
foo:
|
||||
- 1
|
||||
- 2
|
||||
modified:
|
||||
foo:
|
||||
baz: 0
|
||||
current:
|
||||
foo:
|
||||
- 1
|
||||
- 2
|
||||
bar: 2
|
||||
threeWay:
|
||||
foo:
|
||||
baz: 0
|
||||
result:
|
||||
foo:
|
||||
baz: 0
|
||||
bar: 2
|
||||
- description: patch with different nested types
|
||||
original:
|
||||
foo:
|
||||
- a: true
|
||||
- 2
|
||||
- false
|
||||
modified:
|
||||
foo:
|
||||
- 1
|
||||
- false
|
||||
- b: 1
|
||||
current:
|
||||
foo:
|
||||
- a: true
|
||||
- 2
|
||||
- false
|
||||
bar: 0
|
||||
threeWay:
|
||||
foo:
|
||||
- 1
|
||||
- false
|
||||
- b: 1
|
||||
result:
|
||||
foo:
|
||||
- 1
|
||||
- false
|
||||
- b: 1
|
||||
bar: 0
|
||||
`)
|
||||
|
||||
func TestCreateThreeWayJSONMergePatch(t *testing.T) {
|
||||
tc := JSONMergePatchTestCases{}
|
||||
err := yaml.Unmarshal(createJSONMergePatchTestCaseData, &tc)
|
||||
if err != nil {
|
||||
t.Errorf("can't unmarshal test cases: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, c := range tc.TestCases {
|
||||
testThreeWayPatch(t, c)
|
||||
}
|
||||
}
|
||||
|
||||
func testThreeWayPatch(t *testing.T, c JSONMergePatchTestCase) {
|
||||
original, modified, current, expected, result := threeWayTestCaseToJSONOrFail(t, c)
|
||||
actual, err := CreateThreeWayJSONMergePatch(original, modified, current)
|
||||
if err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
testPatchCreation(t, expected, actual, c.Description)
|
||||
testPatchApplication(t, current, actual, result, c.Description)
|
||||
}
|
||||
|
||||
func testPatchCreation(t *testing.T, expected, actual []byte, description string) {
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("error in test case: %s\nexpected patch:\n%s\ngot:\n%s\n",
|
||||
description, jsonToYAMLOrError(expected), jsonToYAMLOrError(actual))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func testPatchApplication(t *testing.T, original, patch, expected []byte, description string) {
|
||||
result, err := jsonpatch.MergePatch(original, patch)
|
||||
if err != nil {
|
||||
t.Errorf("error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n",
|
||||
err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original))
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
format := "error in test case: %s\npatch application failed:\noriginal:\n%s\npatch:\n%s\nexpected:\n%s\ngot:\n%s\n"
|
||||
t.Errorf(format, description,
|
||||
jsonToYAMLOrError(original), jsonToYAMLOrError(patch),
|
||||
jsonToYAMLOrError(expected), jsonToYAMLOrError(result))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func threeWayTestCaseToJSONOrFail(t *testing.T, c JSONMergePatchTestCase) ([]byte, []byte, []byte, []byte, []byte) {
|
||||
return testObjectToJSONOrFail(t, c.Original),
|
||||
testObjectToJSONOrFail(t, c.Modified),
|
||||
testObjectToJSONOrFail(t, c.Current),
|
||||
testObjectToJSONOrFail(t, c.ThreeWay),
|
||||
testObjectToJSONOrFail(t, c.Result)
|
||||
}
|
||||
|
||||
func testObjectToJSONOrFail(t *testing.T, o map[string]interface{}) []byte {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
j, err := toJSON(o)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
func jsonToYAMLOrError(j []byte) string {
|
||||
y, err := jsonToYAML(j)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return string(y)
|
||||
}
|
||||
|
||||
func toJSON(v interface{}) ([]byte, error) {
|
||||
j, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("json marshal failed: %v\n%v\n", err, spew.Sdump(v))
|
||||
}
|
||||
return j, nil
|
||||
}
|
||||
|
||||
func jsonToYAML(j []byte) ([]byte, error) {
|
||||
y, err := yaml.JSONToYAML(j)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("json to yaml failed: %v\n%v\n", err, j)
|
||||
}
|
||||
return y, nil
|
||||
}
|
@ -58,40 +58,40 @@ type JSONMap map[string]interface{}
|
||||
// IsPreconditionFailed returns true if the provided error indicates
|
||||
// a precondition failed.
|
||||
func IsPreconditionFailed(err error) bool {
|
||||
_, ok := err.(errPreconditionFailed)
|
||||
_, ok := err.(ErrPreconditionFailed)
|
||||
return ok
|
||||
}
|
||||
|
||||
type errPreconditionFailed struct {
|
||||
type ErrPreconditionFailed struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func newErrPreconditionFailed(target map[string]interface{}) errPreconditionFailed {
|
||||
func NewErrPreconditionFailed(target map[string]interface{}) ErrPreconditionFailed {
|
||||
s := fmt.Sprintf("precondition failed for: %v", target)
|
||||
return errPreconditionFailed{s}
|
||||
return ErrPreconditionFailed{s}
|
||||
}
|
||||
|
||||
func (err errPreconditionFailed) Error() string {
|
||||
func (err ErrPreconditionFailed) Error() string {
|
||||
return err.message
|
||||
}
|
||||
|
||||
type errConflict struct {
|
||||
type ErrConflict struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func newErrConflict(patch, current string) errConflict {
|
||||
func NewErrConflict(patch, current string) ErrConflict {
|
||||
s := fmt.Sprintf("patch:\n%s\nconflicts with changes made from original to current:\n%s\n", patch, current)
|
||||
return errConflict{s}
|
||||
return ErrConflict{s}
|
||||
}
|
||||
|
||||
func (err errConflict) Error() string {
|
||||
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)
|
||||
_, ok := err.(ErrConflict)
|
||||
return ok
|
||||
}
|
||||
|
||||
@ -187,7 +187,7 @@ func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{
|
||||
// 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 nil, NewErrPreconditionFailed(patchMap)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1340,7 +1340,7 @@ func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct int
|
||||
// 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 nil, NewErrPreconditionFailed(patchMap)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1358,14 +1358,14 @@ func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct int
|
||||
}
|
||||
|
||||
if hasConflicts {
|
||||
return nil, newErrConflict(toYAMLOrError(patchMap), toYAMLOrError(changedMap))
|
||||
return nil, NewErrConflict(ToYAMLOrError(patchMap), ToYAMLOrError(changedMap))
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(patchMap)
|
||||
}
|
||||
|
||||
func toYAMLOrError(v interface{}) string {
|
||||
func ToYAMLOrError(v interface{}) string {
|
||||
y, err := toYAML(v)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
|
@ -266,7 +266,7 @@ func TestSortMergeLists(t *testing.T) {
|
||||
sorted := testObjectToJSONOrFail(t, c.Sorted, c.Description)
|
||||
if !reflect.DeepEqual(original, sorted) {
|
||||
t.Errorf("error in test case: %s\ncannot sort object:\n%s\nexpected:\n%s\ngot:\n%s\n",
|
||||
c.Description, toYAMLOrError(c.Original), toYAMLOrError(c.Sorted), jsonToYAMLOrError(original))
|
||||
c.Description, ToYAMLOrError(c.Original), ToYAMLOrError(c.Sorted), jsonToYAMLOrError(original))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2037,7 +2037,7 @@ func testTwoWayPatch(t *testing.T, c StrategicMergePatchTestCase) {
|
||||
actualPatch, err := CreateTwoWayMergePatch(original, modified, mergeItem)
|
||||
if err != nil {
|
||||
t.Errorf("error: %s\nin test case: %s\ncannot create two way patch: %s:\n%s\n",
|
||||
err, c.Description, original, toYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||
err, c.Description, original, ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||
return
|
||||
}
|
||||
|
||||
@ -2087,13 +2087,13 @@ func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase) {
|
||||
if err != nil {
|
||||
if !IsConflict(err) {
|
||||
t.Errorf("error: %s\nin test case: %s\ncannot create three way patch:\n%s\n",
|
||||
err, c.Description, toYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||
err, c.Description, ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.Contains(c.Description, "conflict") {
|
||||
t.Errorf("unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\n%s\n",
|
||||
err, c.Description, toYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||
err, c.Description, ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||
return
|
||||
}
|
||||
|
||||
@ -2101,7 +2101,7 @@ func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase) {
|
||||
actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, true)
|
||||
if err != nil {
|
||||
t.Errorf("error: %s\nin test case: %s\ncannot force three way patch application:\n%s\n",
|
||||
err, c.Description, toYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||
err, c.Description, ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||
return
|
||||
}
|
||||
|
||||
@ -2114,7 +2114,7 @@ func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase) {
|
||||
|
||||
if strings.Contains(c.Description, "conflict") || len(c.Result) < 1 {
|
||||
t.Errorf("error in test case: %s\nexpected conflict did not occur:\n%s\n",
|
||||
c.Description, toYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||
c.Description, ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||
return
|
||||
}
|
||||
|
||||
|
24
vendor/BUILD
vendored
24
vendor/BUILD
vendored
@ -14192,3 +14192,27 @@ go_library(
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "k8s.io/apimachinery/pkg/util/jsonmergepatch_test",
|
||||
srcs = ["k8s.io/apimachinery/pkg/util/jsonmergepatch/patch_test.go"],
|
||||
library = ":k8s.io/apimachinery/pkg/util/jsonmergepatch",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor:github.com/davecgh/go-spew/spew",
|
||||
"//vendor:github.com/evanphx/json-patch",
|
||||
"//vendor:github.com/ghodss/yaml",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/json",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "k8s.io/apimachinery/pkg/util/jsonmergepatch",
|
||||
srcs = ["k8s.io/apimachinery/pkg/util/jsonmergepatch/patch.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor:github.com/evanphx/json-patch",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/json",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||
],
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user