diff --git a/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch.go b/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch.go new file mode 100644 index 00000000000..5c6adf2f83d --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch.go @@ -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 +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch_test.go b/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch_test.go new file mode 100644 index 00000000000..49967151dd1 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch_test.go @@ -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 +} \ No newline at end of file diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go index 1694aea3376..7ea55e2fb2b 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go @@ -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() diff --git a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go index ad5bd235a61..b29714af719 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go @@ -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 } diff --git a/vendor/BUILD b/vendor/BUILD index 234d2d18c5b..6bd0232ccf4 100644 --- a/vendor/BUILD +++ b/vendor/BUILD @@ -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", + ], +)