mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-12 05:21:58 +00:00
support openapi in strategic merge patch
This commit is contained in:
parent
db4134d03f
commit
f1ad84a2c3
@ -9,26 +9,38 @@ load(
|
|||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = ["patch_test.go"],
|
srcs = ["patch_test.go"],
|
||||||
|
data = [
|
||||||
|
"testdata/swagger-merge-item.json",
|
||||||
|
"testdata/swagger-precision-item.json",
|
||||||
|
],
|
||||||
importpath = "k8s.io/apimachinery/pkg/util/strategicpatch",
|
importpath = "k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
deps = [
|
deps = [
|
||||||
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
||||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch/testing:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = ["patch.go"],
|
srcs = [
|
||||||
|
"errors.go",
|
||||||
|
"meta.go",
|
||||||
|
"patch.go",
|
||||||
|
"types.go",
|
||||||
|
],
|
||||||
importpath = "k8s.io/apimachinery/pkg/util/strategicpatch",
|
importpath = "k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||||
deps = [
|
deps = [
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/third_party/forked/golang/json:go_default_library",
|
"//vendor/k8s.io/apimachinery/third_party/forked/golang/json:go_default_library",
|
||||||
|
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,6 +53,9 @@ filegroup(
|
|||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "all-srcs",
|
name = "all-srcs",
|
||||||
srcs = [":package-srcs"],
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testing:all-srcs",
|
||||||
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
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 strategicpatch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LookupPatchMetaError struct {
|
||||||
|
Path string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e LookupPatchMetaError) Error() string {
|
||||||
|
return fmt.Sprintf("LookupPatchMetaError(%s): %v", e.Path, e.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FieldNotFoundError struct {
|
||||||
|
Path string
|
||||||
|
Field string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e FieldNotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("unable to find api field %q in %s", e.Field, e.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvalidTypeError struct {
|
||||||
|
Path string
|
||||||
|
Expected string
|
||||||
|
Actual string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e InvalidTypeError) Error() string {
|
||||||
|
return fmt.Sprintf("invalid type for %s: got %q, expected %q", e.Path, e.Actual, e.Expected)
|
||||||
|
}
|
194
staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/meta.go
Normal file
194
staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/meta.go
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
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 strategicpatch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/mergepatch"
|
||||||
|
forkedjson "k8s.io/apimachinery/third_party/forked/golang/json"
|
||||||
|
openapi "k8s.io/kube-openapi/pkg/util/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PatchMeta struct {
|
||||||
|
patchStrategies []string
|
||||||
|
patchMergeKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm PatchMeta) GetPatchStrategies() []string {
|
||||||
|
if pm.patchStrategies == nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return pm.patchStrategies
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm PatchMeta) SetPatchStrategies(ps []string) {
|
||||||
|
pm.patchStrategies = ps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm PatchMeta) GetPatchMergeKey() string {
|
||||||
|
return pm.patchMergeKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm PatchMeta) SetPatchMergeKey(pmk string) {
|
||||||
|
pm.patchMergeKey = pmk
|
||||||
|
}
|
||||||
|
|
||||||
|
type LookupPatchMeta interface {
|
||||||
|
// LookupPatchMetadataForStruct gets subschema and the patch metadata (e.g. patch strategy and merge key) for map.
|
||||||
|
LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error)
|
||||||
|
// LookupPatchMetadataForSlice get subschema and the patch metadata for slice.
|
||||||
|
LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error)
|
||||||
|
// Get the type name of the field
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PatchMetaFromStruct struct {
|
||||||
|
T reflect.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPatchMetaFromStruct(dataStruct interface{}) (PatchMetaFromStruct, error) {
|
||||||
|
t, err := getTagStructType(dataStruct)
|
||||||
|
return PatchMetaFromStruct{T: t}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ LookupPatchMeta = PatchMetaFromStruct{}
|
||||||
|
|
||||||
|
func (s PatchMetaFromStruct) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) {
|
||||||
|
fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadataForStruct(s.T, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, PatchMeta{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatchMetaFromStruct{T: fieldType},
|
||||||
|
PatchMeta{
|
||||||
|
patchStrategies: fieldPatchStrategies,
|
||||||
|
patchMergeKey: fieldPatchMergeKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PatchMetaFromStruct) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) {
|
||||||
|
subschema, patchMeta, err := s.LookupPatchMetadataForStruct(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, PatchMeta{}, err
|
||||||
|
}
|
||||||
|
elemPatchMetaFromStruct := subschema.(PatchMetaFromStruct)
|
||||||
|
t := elemPatchMetaFromStruct.T
|
||||||
|
|
||||||
|
var elemType reflect.Type
|
||||||
|
switch t.Kind() {
|
||||||
|
// If t is an array or a slice, get the element type.
|
||||||
|
// If element is still an array or a slice, return an error.
|
||||||
|
// Otherwise, return element type.
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
elemType = t.Elem()
|
||||||
|
if elemType.Kind() == reflect.Array || elemType.Kind() == reflect.Slice {
|
||||||
|
return nil, PatchMeta{}, errors.New("unexpected slice of slice")
|
||||||
|
}
|
||||||
|
// If t is an pointer, get the underlying element.
|
||||||
|
// If the underlying element is neither an array nor a slice, the pointer is pointing to a slice,
|
||||||
|
// e.g. https://github.com/kubernetes/kubernetes/blob/bc22e206c79282487ea0bf5696d5ccec7e839a76/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go#L2782-L2822
|
||||||
|
// If the underlying element is either an array or a slice, return its element type.
|
||||||
|
case reflect.Ptr:
|
||||||
|
t = t.Elem()
|
||||||
|
if t.Kind() == reflect.Array || t.Kind() == reflect.Slice {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
elemType = t
|
||||||
|
default:
|
||||||
|
return nil, PatchMeta{}, fmt.Errorf("expected slice or array type, but got: %s", s.T.Kind().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatchMetaFromStruct{T: elemType}, patchMeta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PatchMetaFromStruct) Name() string {
|
||||||
|
return s.T.Kind().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTagStructType(dataStruct interface{}) (reflect.Type, error) {
|
||||||
|
if dataStruct == nil {
|
||||||
|
return nil, mergepatch.ErrBadArgKind(struct{}{}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := reflect.TypeOf(dataStruct)
|
||||||
|
// Get the underlying type for pointers
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return nil, mergepatch.ErrBadArgKind(struct{}{}, dataStruct)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTagStructTypeOrDie(dataStruct interface{}) reflect.Type {
|
||||||
|
t, err := getTagStructType(dataStruct)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
type PatchMetaFromOpenAPI struct {
|
||||||
|
Schema openapi.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPatchMetaFromOpenAPI(s openapi.Schema) PatchMetaFromOpenAPI {
|
||||||
|
return PatchMetaFromOpenAPI{Schema: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ LookupPatchMeta = PatchMetaFromOpenAPI{}
|
||||||
|
|
||||||
|
func (s PatchMetaFromOpenAPI) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) {
|
||||||
|
if s.Schema == nil {
|
||||||
|
return nil, PatchMeta{}, nil
|
||||||
|
}
|
||||||
|
kindItem := NewKindItem(key, s.Schema.GetPath())
|
||||||
|
s.Schema.Accept(kindItem)
|
||||||
|
|
||||||
|
err := kindItem.Error()
|
||||||
|
if err != nil {
|
||||||
|
return nil, PatchMeta{}, err
|
||||||
|
}
|
||||||
|
return PatchMetaFromOpenAPI{Schema: kindItem.subschema},
|
||||||
|
kindItem.patchmeta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PatchMetaFromOpenAPI) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) {
|
||||||
|
if s.Schema == nil {
|
||||||
|
return nil, PatchMeta{}, nil
|
||||||
|
}
|
||||||
|
sliceItem := NewSliceItem(key, s.Schema.GetPath())
|
||||||
|
s.Schema.Accept(sliceItem)
|
||||||
|
|
||||||
|
err := sliceItem.Error()
|
||||||
|
if err != nil {
|
||||||
|
return nil, PatchMeta{}, err
|
||||||
|
}
|
||||||
|
return PatchMetaFromOpenAPI{Schema: sliceItem.subschema},
|
||||||
|
sliceItem.patchmeta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PatchMetaFromOpenAPI) Name() string {
|
||||||
|
schema := s.Schema
|
||||||
|
return schema.GetName()
|
||||||
|
}
|
@ -25,7 +25,6 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/util/json"
|
"k8s.io/apimachinery/pkg/util/json"
|
||||||
"k8s.io/apimachinery/pkg/util/mergepatch"
|
"k8s.io/apimachinery/pkg/util/mergepatch"
|
||||||
forkedjson "k8s.io/apimachinery/third_party/forked/golang/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// An alternate implementation of JSON Merge Patch
|
// An alternate implementation of JSON Merge Patch
|
||||||
@ -93,6 +92,16 @@ type MergeOptions struct {
|
|||||||
// return a patch that yields the modified document when applied to the original document, or an error
|
// 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.
|
// if either of the two documents is invalid.
|
||||||
func CreateTwoWayMergePatch(original, modified []byte, dataStruct interface{}, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
|
func CreateTwoWayMergePatch(original, modified []byte, dataStruct interface{}, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
|
||||||
|
schema, err := NewPatchMetaFromStruct(dataStruct)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema, fns...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTwoWayMergePatchUsingLookupPatchMeta(
|
||||||
|
original, modified []byte, schema LookupPatchMeta, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
|
||||||
originalMap := map[string]interface{}{}
|
originalMap := map[string]interface{}{}
|
||||||
if len(original) > 0 {
|
if len(original) > 0 {
|
||||||
if err := json.Unmarshal(original, &originalMap); err != nil {
|
if err := json.Unmarshal(original, &originalMap); err != nil {
|
||||||
@ -107,7 +116,7 @@ func CreateTwoWayMergePatch(original, modified []byte, dataStruct interface{}, f
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
patchMap, err := CreateTwoWayMergeMapPatch(originalMap, modifiedMap, dataStruct, fns...)
|
patchMap, err := CreateTwoWayMergeMapPatchUsingLookupPatchMeta(originalMap, modifiedMap, schema, fns...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -119,15 +128,19 @@ func CreateTwoWayMergePatch(original, modified []byte, dataStruct interface{}, f
|
|||||||
// encoded JSONMap.
|
// encoded JSONMap.
|
||||||
// The serialized version of the map can then be passed to StrategicMergeMapPatch.
|
// The serialized version of the map can then be passed to StrategicMergeMapPatch.
|
||||||
func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{}, fns ...mergepatch.PreconditionFunc) (JSONMap, error) {
|
func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{}, fns ...mergepatch.PreconditionFunc) (JSONMap, error) {
|
||||||
t, err := getTagStructType(dataStruct)
|
schema, err := NewPatchMetaFromStruct(dataStruct)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return CreateTwoWayMergeMapPatchUsingLookupPatchMeta(original, modified, schema, fns...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTwoWayMergeMapPatchUsingLookupPatchMeta(original, modified JSONMap, schema LookupPatchMeta, fns ...mergepatch.PreconditionFunc) (JSONMap, error) {
|
||||||
diffOptions := DiffOptions{
|
diffOptions := DiffOptions{
|
||||||
SetElementOrder: true,
|
SetElementOrder: true,
|
||||||
}
|
}
|
||||||
patchMap, err := diffMaps(original, modified, t, diffOptions)
|
patchMap, err := diffMaps(original, modified, schema, diffOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -152,12 +165,9 @@ func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{
|
|||||||
// - IFF list of primitives && merge strategy - use parallel deletion list
|
// - IFF list of primitives && merge strategy - use parallel deletion list
|
||||||
// - IFF list of maps or primitives with replace strategy (default) - set patch value to the value in modified
|
// - IFF list of maps or primitives with replace strategy (default) - set patch value to the value in modified
|
||||||
// - Build $retainKeys directive for fields with retainKeys patch strategy
|
// - Build $retainKeys directive for fields with retainKeys patch strategy
|
||||||
func diffMaps(original, modified map[string]interface{}, t reflect.Type, diffOptions DiffOptions) (map[string]interface{}, error) {
|
func diffMaps(original, modified map[string]interface{}, schema LookupPatchMeta, diffOptions DiffOptions) (map[string]interface{}, error) {
|
||||||
patch := map[string]interface{}{}
|
patch := map[string]interface{}{}
|
||||||
// Get the underlying type for pointers
|
|
||||||
if t.Kind() == reflect.Ptr {
|
|
||||||
t = t.Elem()
|
|
||||||
}
|
|
||||||
// This will be used to build the $retainKeys directive sent in the patch
|
// This will be used to build the $retainKeys directive sent in the patch
|
||||||
retainKeysList := make([]interface{}, 0, len(modified))
|
retainKeysList := make([]interface{}, 0, len(modified))
|
||||||
|
|
||||||
@ -199,10 +209,10 @@ func diffMaps(original, modified map[string]interface{}, t reflect.Type, diffOpt
|
|||||||
switch originalValueTyped := originalValue.(type) {
|
switch originalValueTyped := originalValue.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
modifiedValueTyped := modifiedValue.(map[string]interface{})
|
modifiedValueTyped := modifiedValue.(map[string]interface{})
|
||||||
err = handleMapDiff(key, originalValueTyped, modifiedValueTyped, patch, t, diffOptions)
|
err = handleMapDiff(key, originalValueTyped, modifiedValueTyped, patch, schema, diffOptions)
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
modifiedValueTyped := modifiedValue.([]interface{})
|
modifiedValueTyped := modifiedValue.([]interface{})
|
||||||
err = handleSliceDiff(key, originalValueTyped, modifiedValueTyped, patch, t, diffOptions)
|
err = handleSliceDiff(key, originalValueTyped, modifiedValueTyped, patch, schema, diffOptions)
|
||||||
default:
|
default:
|
||||||
replacePatchFieldIfNotEqual(key, originalValue, modifiedValue, patch, diffOptions)
|
replacePatchFieldIfNotEqual(key, originalValue, modifiedValue, patch, diffOptions)
|
||||||
}
|
}
|
||||||
@ -249,8 +259,9 @@ func handleDirectiveMarker(key string, originalValue, modifiedValue interface{},
|
|||||||
// patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue
|
// patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue
|
||||||
// diffOptions contains multiple options to control how we do the diff.
|
// diffOptions contains multiple options to control how we do the diff.
|
||||||
func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]interface{},
|
func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]interface{},
|
||||||
t reflect.Type, diffOptions DiffOptions) error {
|
schema LookupPatchMeta, diffOptions DiffOptions) error {
|
||||||
fieldType, fieldPatchStrategies, _, err := forkedjson.LookupPatchMetadata(t, key)
|
subschema, patchMeta, err := schema.LookupPatchMetadataForStruct(key)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We couldn't look up metadata for the field
|
// We couldn't look up metadata for the field
|
||||||
// If the values are identical, this doesn't matter, no patch is needed
|
// If the values are identical, this doesn't matter, no patch is needed
|
||||||
@ -260,7 +271,7 @@ func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]in
|
|||||||
// Otherwise, return the error
|
// Otherwise, return the error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies)
|
retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -272,7 +283,7 @@ func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]in
|
|||||||
patch[key] = modifiedValue
|
patch[key] = modifiedValue
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
patchValue, err := diffMaps(originalValue, modifiedValue, fieldType, diffOptions)
|
patchValue, err := diffMaps(originalValue, modifiedValue, subschema, diffOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -291,8 +302,8 @@ func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]in
|
|||||||
// patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue
|
// patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue
|
||||||
// diffOptions contains multiple options to control how we do the diff.
|
// diffOptions contains multiple options to control how we do the diff.
|
||||||
func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, patch map[string]interface{},
|
func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, patch map[string]interface{},
|
||||||
t reflect.Type, diffOptions DiffOptions) error {
|
schema LookupPatchMeta, diffOptions DiffOptions) error {
|
||||||
fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, key)
|
subschema, patchMeta, err := schema.LookupPatchMetadataForSlice(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We couldn't look up metadata for the field
|
// We couldn't look up metadata for the field
|
||||||
// If the values are identical, this doesn't matter, no patch is needed
|
// If the values are identical, this doesn't matter, no patch is needed
|
||||||
@ -302,7 +313,7 @@ func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, pat
|
|||||||
// Otherwise, return the error
|
// Otherwise, return the error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies)
|
retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -310,7 +321,7 @@ func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, pat
|
|||||||
// Merge the 2 slices using mergePatchKey
|
// Merge the 2 slices using mergePatchKey
|
||||||
case mergeDirective:
|
case mergeDirective:
|
||||||
diffOptions.BuildRetainKeysDirective = retainKeys
|
diffOptions.BuildRetainKeysDirective = retainKeys
|
||||||
addList, deletionList, setOrderList, err := diffLists(originalValue, modifiedValue, fieldType.Elem(), fieldPatchMergeKey, diffOptions)
|
addList, deletionList, setOrderList, err := diffLists(originalValue, modifiedValue, subschema, patchMeta.GetPatchMergeKey(), diffOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -537,7 +548,7 @@ func normalizeSliceOrder(toSort, order []interface{}, mergeKey string, kind refl
|
|||||||
// another list to set the order of the list
|
// another list to set the order of the list
|
||||||
// Only list of primitives with merge strategy will generate a parallel deletion list.
|
// Only list of primitives with merge strategy will generate a parallel deletion list.
|
||||||
// These two lists should yield modified when applied to original, for lists with merge semantics.
|
// These two lists should yield modified when applied to original, for lists with merge semantics.
|
||||||
func diffLists(original, modified []interface{}, t reflect.Type, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, []interface{}, error) {
|
func diffLists(original, modified []interface{}, schema LookupPatchMeta, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, []interface{}, error) {
|
||||||
if len(original) == 0 {
|
if len(original) == 0 {
|
||||||
// Both slices are empty - do nothing
|
// Both slices are empty - do nothing
|
||||||
if len(modified) == 0 || diffOptions.IgnoreChangesAndAdditions {
|
if len(modified) == 0 || diffOptions.IgnoreChangesAndAdditions {
|
||||||
@ -557,7 +568,7 @@ func diffLists(original, modified []interface{}, t reflect.Type, mergeKey string
|
|||||||
kind := elementType.Kind()
|
kind := elementType.Kind()
|
||||||
switch kind {
|
switch kind {
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
patchList, deleteList, err = diffListsOfMaps(original, modified, t, mergeKey, diffOptions)
|
patchList, deleteList, err = diffListsOfMaps(original, modified, schema, mergeKey, diffOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
@ -703,15 +714,15 @@ func compareListValuesAtIndex(list1Inbounds, list2Inbounds bool, list1Value, lis
|
|||||||
// diffListsOfMaps takes a pair of lists and
|
// diffListsOfMaps takes a pair of lists and
|
||||||
// returns a (recursive) strategic merge patch list contains additions and changes and
|
// returns a (recursive) strategic merge patch list contains additions and changes and
|
||||||
// a deletion list contains deletions
|
// a deletion list contains deletions
|
||||||
func diffListsOfMaps(original, modified []interface{}, t reflect.Type, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, error) {
|
func diffListsOfMaps(original, modified []interface{}, schema LookupPatchMeta, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, error) {
|
||||||
patch := make([]interface{}, 0, len(modified))
|
patch := make([]interface{}, 0, len(modified))
|
||||||
deletionList := make([]interface{}, 0, len(original))
|
deletionList := make([]interface{}, 0, len(original))
|
||||||
|
|
||||||
originalSorted, err := sortMergeListsByNameArray(original, t, mergeKey, false)
|
originalSorted, err := sortMergeListsByNameArray(original, schema, mergeKey, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
modifiedSorted, err := sortMergeListsByNameArray(modified, t, mergeKey, false)
|
modifiedSorted, err := sortMergeListsByNameArray(modified, schema, mergeKey, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -746,7 +757,7 @@ func diffListsOfMaps(original, modified []interface{}, t reflect.Type, mergeKey
|
|||||||
switch {
|
switch {
|
||||||
case bothInBounds && ItemMatchesOriginalAndModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString):
|
case bothInBounds && ItemMatchesOriginalAndModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString):
|
||||||
// Merge key values are equal, so recurse
|
// Merge key values are equal, so recurse
|
||||||
patchValue, err := diffMaps(originalElement, modifiedElement, t, diffOptions)
|
patchValue, err := diffMaps(originalElement, modifiedElement, schema, diffOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -799,6 +810,15 @@ func getMapAndMergeKeyValueByIndex(index int, mergeKey string, listOfMaps []inte
|
|||||||
// must be json encoded content. A patch can be created from an original and a modified document
|
// must be json encoded content. A patch can be created from an original and a modified document
|
||||||
// by calling CreateStrategicMergePatch.
|
// by calling CreateStrategicMergePatch.
|
||||||
func StrategicMergePatch(original, patch []byte, dataStruct interface{}) ([]byte, error) {
|
func StrategicMergePatch(original, patch []byte, dataStruct interface{}) ([]byte, error) {
|
||||||
|
schema, err := NewPatchMetaFromStruct(dataStruct)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return StrategicMergePatchUsingLookupPatchMeta(original, patch, schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StrategicMergePatchUsingLookupPatchMeta(original, patch []byte, schema LookupPatchMeta) ([]byte, error) {
|
||||||
originalMap, err := handleUnmarshal(original)
|
originalMap, err := handleUnmarshal(original)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -808,7 +828,7 @@ func StrategicMergePatch(original, patch []byte, dataStruct interface{}) ([]byte
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := StrategicMergeMapPatch(originalMap, patchMap, dataStruct)
|
result, err := StrategicMergeMapPatchUsingLookupPatchMeta(originalMap, patchMap, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -834,7 +854,7 @@ func handleUnmarshal(j []byte) (map[string]interface{}, error) {
|
|||||||
// calling CreateTwoWayMergeMapPatch.
|
// calling CreateTwoWayMergeMapPatch.
|
||||||
// Warning: the original and patch JSONMap objects are mutated by this function and should not be reused.
|
// Warning: the original and patch JSONMap objects are mutated by this function and should not be reused.
|
||||||
func StrategicMergeMapPatch(original, patch JSONMap, dataStruct interface{}) (JSONMap, error) {
|
func StrategicMergeMapPatch(original, patch JSONMap, dataStruct interface{}) (JSONMap, error) {
|
||||||
t, err := getTagStructType(dataStruct)
|
schema, err := NewPatchMetaFromStruct(dataStruct)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -849,29 +869,15 @@ func StrategicMergeMapPatch(original, patch JSONMap, dataStruct interface{}) (JS
|
|||||||
return nil, mergepatch.ErrUnsupportedStrategicMergePatchFormat
|
return nil, mergepatch.ErrUnsupportedStrategicMergePatchFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return StrategicMergeMapPatchUsingLookupPatchMeta(original, patch, schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StrategicMergeMapPatchUsingLookupPatchMeta(original, patch JSONMap, schema LookupPatchMeta) (JSONMap, error) {
|
||||||
mergeOptions := MergeOptions{
|
mergeOptions := MergeOptions{
|
||||||
MergeParallelList: true,
|
MergeParallelList: true,
|
||||||
IgnoreUnmatchedNulls: true,
|
IgnoreUnmatchedNulls: true,
|
||||||
}
|
}
|
||||||
return mergeMap(original, patch, t, mergeOptions)
|
return mergeMap(original, patch, schema, mergeOptions)
|
||||||
}
|
|
||||||
|
|
||||||
func getTagStructType(dataStruct interface{}) (reflect.Type, error) {
|
|
||||||
if dataStruct == nil {
|
|
||||||
return nil, mergepatch.ErrBadArgKind(struct{}{}, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
t := reflect.TypeOf(dataStruct)
|
|
||||||
// Get the underlying type for pointers
|
|
||||||
if t.Kind() == reflect.Ptr {
|
|
||||||
t = t.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Kind() != reflect.Struct {
|
|
||||||
return nil, mergepatch.ErrBadArgKind(struct{}{}, dataStruct)
|
|
||||||
}
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDirectiveInMergeMap handles the patch directive when merging 2 maps.
|
// handleDirectiveInMergeMap handles the patch directive when merging 2 maps.
|
||||||
@ -1079,7 +1085,7 @@ func applyRetainKeysDirective(original, patch map[string]interface{}, options Me
|
|||||||
// The precedence is $setElementOrder > order in patch list > order in live list.
|
// The precedence is $setElementOrder > order in patch list > order in live list.
|
||||||
// This function will delete the item after merging it to prevent process it again in the future.
|
// This function will delete the item after merging it to prevent process it again in the future.
|
||||||
// Ref: https://git.k8s.io/community/contributors/design-proposals/cli/preserve-order-in-strategic-merge-patch.md
|
// Ref: https://git.k8s.io/community/contributors/design-proposals/cli/preserve-order-in-strategic-merge-patch.md
|
||||||
func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Type, mergeOptions MergeOptions) error {
|
func mergePatchIntoOriginal(original, patch map[string]interface{}, schema LookupPatchMeta, mergeOptions MergeOptions) error {
|
||||||
for key, patchV := range patch {
|
for key, patchV := range patch {
|
||||||
// Do nothing if there is no ordering directive
|
// Do nothing if there is no ordering directive
|
||||||
if !strings.HasPrefix(key, setElementOrderDirectivePrefix) {
|
if !strings.HasPrefix(key, setElementOrderDirectivePrefix) {
|
||||||
@ -1106,9 +1112,9 @@ func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Ty
|
|||||||
var (
|
var (
|
||||||
ok bool
|
ok bool
|
||||||
originalFieldValue, patchFieldValue, merged []interface{}
|
originalFieldValue, patchFieldValue, merged []interface{}
|
||||||
patchStrategy, mergeKey string
|
patchStrategy string
|
||||||
patchStrategies []string
|
patchMeta PatchMeta
|
||||||
fieldType reflect.Type
|
subschema LookupPatchMeta
|
||||||
)
|
)
|
||||||
typedSetElementOrderList, ok := setElementOrderInPatch.([]interface{})
|
typedSetElementOrderList, ok := setElementOrderInPatch.([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -1134,16 +1140,16 @@ func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Ty
|
|||||||
return mergepatch.ErrBadArgType(patchFieldValue, patchList)
|
return mergepatch.ErrBadArgType(patchFieldValue, patchList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fieldType, patchStrategies, mergeKey, err = forkedjson.LookupPatchMetadata(t, originalKey)
|
subschema, patchMeta, err = schema.LookupPatchMetadataForSlice(originalKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, patchStrategy, err = extractRetainKeysPatchStrategy(patchStrategies)
|
_, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Check for consistency between the element order list and the field it applies to
|
// Check for consistency between the element order list and the field it applies to
|
||||||
err = validatePatchWithSetOrderList(patchFieldValue, typedSetElementOrderList, mergeKey)
|
err = validatePatchWithSetOrderList(patchFieldValue, typedSetElementOrderList, patchMeta.GetPatchMergeKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1156,8 +1162,8 @@ func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Ty
|
|||||||
// list was added
|
// list was added
|
||||||
merged = patchFieldValue
|
merged = patchFieldValue
|
||||||
case foundOriginal && foundPatch:
|
case foundOriginal && foundPatch:
|
||||||
merged, err = mergeSliceHandler(originalList, patchList, fieldType,
|
merged, err = mergeSliceHandler(originalList, patchList, subschema,
|
||||||
patchStrategy, mergeKey, false, mergeOptions)
|
patchStrategy, patchMeta.GetPatchMergeKey(), false, mergeOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1167,13 +1173,13 @@ func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Ty
|
|||||||
|
|
||||||
// Split all items into patch items and server-only items and then enforce the order.
|
// Split all items into patch items and server-only items and then enforce the order.
|
||||||
var patchItems, serverOnlyItems []interface{}
|
var patchItems, serverOnlyItems []interface{}
|
||||||
if len(mergeKey) == 0 {
|
if len(patchMeta.GetPatchMergeKey()) == 0 {
|
||||||
// Primitives doesn't need merge key to do partitioning.
|
// Primitives doesn't need merge key to do partitioning.
|
||||||
patchItems, serverOnlyItems = partitionPrimitivesByPresentInList(merged, typedSetElementOrderList)
|
patchItems, serverOnlyItems = partitionPrimitivesByPresentInList(merged, typedSetElementOrderList)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Maps need merge key to do partitioning.
|
// Maps need merge key to do partitioning.
|
||||||
patchItems, serverOnlyItems, err = partitionMapsByPresentInList(merged, typedSetElementOrderList, mergeKey)
|
patchItems, serverOnlyItems, err = partitionMapsByPresentInList(merged, typedSetElementOrderList, patchMeta.GetPatchMergeKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1187,7 +1193,7 @@ func mergePatchIntoOriginal(original, patch map[string]interface{}, t reflect.Ty
|
|||||||
// normalize merged list
|
// normalize merged list
|
||||||
// typedSetElementOrderList contains all the relative order in typedPatchList,
|
// typedSetElementOrderList contains all the relative order in typedPatchList,
|
||||||
// so don't need to use typedPatchList
|
// so don't need to use typedPatchList
|
||||||
both, err := normalizeElementOrder(patchItems, serverOnlyItems, typedSetElementOrderList, originalFieldValue, mergeKey, kind)
|
both, err := normalizeElementOrder(patchItems, serverOnlyItems, typedSetElementOrderList, originalFieldValue, patchMeta.GetPatchMergeKey(), kind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1249,7 +1255,7 @@ func partitionMapsByPresentInList(original, partitionBy []interface{}, mergeKey
|
|||||||
// If patch contains any null field (e.g. field_1: null) that is not
|
// 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
|
// present in original, then to propagate it to the end result use
|
||||||
// mergeOptions.IgnoreUnmatchedNulls == false.
|
// mergeOptions.IgnoreUnmatchedNulls == false.
|
||||||
func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptions MergeOptions) (map[string]interface{}, error) {
|
func mergeMap(original, patch map[string]interface{}, schema LookupPatchMeta, mergeOptions MergeOptions) (map[string]interface{}, error) {
|
||||||
if v, ok := patch[directiveMarker]; ok {
|
if v, ok := patch[directiveMarker]; ok {
|
||||||
return handleDirectiveInMergeMap(v, patch)
|
return handleDirectiveInMergeMap(v, patch)
|
||||||
}
|
}
|
||||||
@ -1269,7 +1275,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio
|
|||||||
// When not merging the directive, it will make sure $setElementOrder list exist only in original.
|
// When not merging the directive, it will make sure $setElementOrder list exist only in original.
|
||||||
// When merging the directive, it will process $setElementOrder and its patch list together.
|
// When merging the directive, it will process $setElementOrder and its patch list together.
|
||||||
// This function will delete the merged elements from patch so they will not be reprocessed
|
// This function will delete the merged elements from patch so they will not be reprocessed
|
||||||
err = mergePatchIntoOriginal(original, patch, t, mergeOptions)
|
err = mergePatchIntoOriginal(original, patch, schema, mergeOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1307,11 +1313,6 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the data type is a pointer, resolve the element.
|
|
||||||
if t.Kind() == reflect.Ptr {
|
|
||||||
t = t.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
originalType := reflect.TypeOf(original[k])
|
originalType := reflect.TypeOf(original[k])
|
||||||
patchType := reflect.TypeOf(patchV)
|
patchType := reflect.TypeOf(patchV)
|
||||||
if originalType != patchType {
|
if originalType != patchType {
|
||||||
@ -1319,22 +1320,27 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// If they're both maps or lists, recurse into the value.
|
// If they're both maps or lists, recurse into the value.
|
||||||
// First find the fieldPatchStrategy and fieldPatchMergeKey.
|
|
||||||
fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, k)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch originalType.Kind() {
|
switch originalType.Kind() {
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
|
subschema, patchMeta, err := schema.LookupPatchMetadataForStruct(k)
|
||||||
original[k], err = mergeMapHandler(original[k], patchV, fieldType, patchStrategy, mergeOptions)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
original[k], err = mergeMapHandler(original[k], patchV, subschema, patchStrategy, mergeOptions)
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
original[k], err = mergeSliceHandler(original[k], patchV, fieldType, patchStrategy, fieldPatchMergeKey, isDeleteList, mergeOptions)
|
subschema, patchMeta, err := schema.LookupPatchMetadataForSlice(k)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
original[k], err = mergeSliceHandler(original[k], patchV, subschema, patchStrategy, patchMeta.GetPatchMergeKey(), isDeleteList, mergeOptions)
|
||||||
default:
|
default:
|
||||||
original[k] = patchV
|
original[k] = patchV
|
||||||
}
|
}
|
||||||
@ -1347,7 +1353,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type, mergeOptio
|
|||||||
|
|
||||||
// mergeMapHandler handles how to merge `patchV` whose key is `key` with `original` respecting
|
// mergeMapHandler handles how to merge `patchV` whose key is `key` with `original` respecting
|
||||||
// fieldPatchStrategy and mergeOptions.
|
// fieldPatchStrategy and mergeOptions.
|
||||||
func mergeMapHandler(original, patch interface{}, fieldType reflect.Type,
|
func mergeMapHandler(original, patch interface{}, schema LookupPatchMeta,
|
||||||
fieldPatchStrategy string, mergeOptions MergeOptions) (map[string]interface{}, error) {
|
fieldPatchStrategy string, mergeOptions MergeOptions) (map[string]interface{}, error) {
|
||||||
typedOriginal, typedPatch, err := mapTypeAssertion(original, patch)
|
typedOriginal, typedPatch, err := mapTypeAssertion(original, patch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1355,7 +1361,7 @@ func mergeMapHandler(original, patch interface{}, fieldType reflect.Type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fieldPatchStrategy != replaceDirective {
|
if fieldPatchStrategy != replaceDirective {
|
||||||
return mergeMap(typedOriginal, typedPatch, fieldType, mergeOptions)
|
return mergeMap(typedOriginal, typedPatch, schema, mergeOptions)
|
||||||
} else {
|
} else {
|
||||||
return typedPatch, nil
|
return typedPatch, nil
|
||||||
}
|
}
|
||||||
@ -1363,7 +1369,7 @@ func mergeMapHandler(original, patch interface{}, fieldType reflect.Type,
|
|||||||
|
|
||||||
// mergeSliceHandler handles how to merge `patchV` whose key is `key` with `original` respecting
|
// mergeSliceHandler handles how to merge `patchV` whose key is `key` with `original` respecting
|
||||||
// fieldPatchStrategy, fieldPatchMergeKey, isDeleteList and mergeOptions.
|
// fieldPatchStrategy, fieldPatchMergeKey, isDeleteList and mergeOptions.
|
||||||
func mergeSliceHandler(original, patch interface{}, fieldType reflect.Type,
|
func mergeSliceHandler(original, patch interface{}, schema LookupPatchMeta,
|
||||||
fieldPatchStrategy, fieldPatchMergeKey string, isDeleteList bool, mergeOptions MergeOptions) ([]interface{}, error) {
|
fieldPatchStrategy, fieldPatchMergeKey string, isDeleteList bool, mergeOptions MergeOptions) ([]interface{}, error) {
|
||||||
typedOriginal, typedPatch, err := sliceTypeAssertion(original, patch)
|
typedOriginal, typedPatch, err := sliceTypeAssertion(original, patch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1371,8 +1377,7 @@ func mergeSliceHandler(original, patch interface{}, fieldType reflect.Type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fieldPatchStrategy == mergeDirective {
|
if fieldPatchStrategy == mergeDirective {
|
||||||
elemType := fieldType.Elem()
|
return mergeSlice(typedOriginal, typedPatch, schema, fieldPatchMergeKey, mergeOptions, isDeleteList)
|
||||||
return mergeSlice(typedOriginal, typedPatch, elemType, fieldPatchMergeKey, mergeOptions, isDeleteList)
|
|
||||||
} else {
|
} else {
|
||||||
return typedPatch, nil
|
return typedPatch, nil
|
||||||
}
|
}
|
||||||
@ -1381,7 +1386,7 @@ func mergeSliceHandler(original, patch interface{}, fieldType reflect.Type,
|
|||||||
// 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, mergeOptions MergeOptions, isDeleteList bool) ([]interface{}, error) {
|
func mergeSlice(original, patch []interface{}, schema LookupPatchMeta, mergeKey string, mergeOptions MergeOptions, isDeleteList bool) ([]interface{}, error) {
|
||||||
if len(original) == 0 && len(patch) == 0 {
|
if len(original) == 0 && len(patch) == 0 {
|
||||||
return original, nil
|
return original, nil
|
||||||
}
|
}
|
||||||
@ -1406,7 +1411,7 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
if mergeKey == "" {
|
if mergeKey == "" {
|
||||||
return nil, fmt.Errorf("cannot merge lists without merge key for type %s", elemType.Kind().String())
|
return nil, fmt.Errorf("cannot merge lists without merge key for %s", schema.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
original, patch, err = mergeSliceWithSpecialElements(original, patch, mergeKey)
|
original, patch, err = mergeSliceWithSpecialElements(original, patch, mergeKey)
|
||||||
@ -1414,7 +1419,7 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
merged, err = mergeSliceWithoutSpecialElements(original, patch, mergeKey, elemType, mergeOptions)
|
merged, err = mergeSliceWithoutSpecialElements(original, patch, mergeKey, schema, mergeOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1492,7 +1497,7 @@ func deleteMatchingEntries(original []interface{}, mergeKey string, mergeValue i
|
|||||||
|
|
||||||
// mergeSliceWithoutSpecialElements merges slices with non-special elements.
|
// mergeSliceWithoutSpecialElements merges slices with non-special elements.
|
||||||
// original and patch must be slices of maps, they should be checked before calling this function.
|
// original and patch must be slices of maps, they should be checked before calling this function.
|
||||||
func mergeSliceWithoutSpecialElements(original, patch []interface{}, mergeKey string, elemType reflect.Type, mergeOptions MergeOptions) ([]interface{}, error) {
|
func mergeSliceWithoutSpecialElements(original, patch []interface{}, mergeKey string, schema LookupPatchMeta, mergeOptions MergeOptions) ([]interface{}, error) {
|
||||||
for _, v := range patch {
|
for _, v := range patch {
|
||||||
typedV := v.(map[string]interface{})
|
typedV := v.(map[string]interface{})
|
||||||
mergeValue, ok := typedV[mergeKey]
|
mergeValue, ok := typedV[mergeKey]
|
||||||
@ -1511,7 +1516,7 @@ func mergeSliceWithoutSpecialElements(original, patch []interface{}, mergeKey st
|
|||||||
var mergedMaps interface{}
|
var mergedMaps interface{}
|
||||||
var err error
|
var err error
|
||||||
// Merge into original.
|
// Merge into original.
|
||||||
mergedMaps, err = mergeMap(originalMap, typedV, elemType, mergeOptions)
|
mergedMaps, err = mergeMap(originalMap, typedV, schema, mergeOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1560,14 +1565,14 @@ func findMapInSliceBasedOnKeyValue(m []interface{}, key string, value interface{
|
|||||||
// by key. This is needed by tests because in JSON, list order is significant,
|
// by key. This is needed by tests because in JSON, list order is significant,
|
||||||
// but in Strategic Merge Patch, merge lists do not have significant order.
|
// but in Strategic Merge Patch, merge lists do not have significant order.
|
||||||
// Sorting the lists allows for order-insensitive comparison of patched maps.
|
// Sorting the lists allows for order-insensitive comparison of patched maps.
|
||||||
func sortMergeListsByName(mapJSON []byte, dataStruct interface{}) ([]byte, error) {
|
func sortMergeListsByName(mapJSON []byte, schema LookupPatchMeta) ([]byte, error) {
|
||||||
var m map[string]interface{}
|
var m map[string]interface{}
|
||||||
err := json.Unmarshal(mapJSON, &m)
|
err := json.Unmarshal(mapJSON, &m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, mergepatch.ErrBadJSONDoc
|
return nil, mergepatch.ErrBadJSONDoc
|
||||||
}
|
}
|
||||||
|
|
||||||
newM, err := sortMergeListsByNameMap(m, reflect.TypeOf(dataStruct))
|
newM, err := sortMergeListsByNameMap(m, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1576,7 +1581,7 @@ func sortMergeListsByName(mapJSON []byte, dataStruct interface{}) ([]byte, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in a map.
|
// Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in a map.
|
||||||
func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[string]interface{}, error) {
|
func sortMergeListsByNameMap(s map[string]interface{}, schema LookupPatchMeta) (map[string]interface{}, error) {
|
||||||
newS := map[string]interface{}{}
|
newS := map[string]interface{}{}
|
||||||
for k, v := range s {
|
for k, v := range s {
|
||||||
if k == retainKeysDirective {
|
if k == retainKeysDirective {
|
||||||
@ -1597,26 +1602,29 @@ func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[stri
|
|||||||
return nil, mergepatch.ErrBadPatchFormatForSetElementOrderList
|
return nil, mergepatch.ErrBadPatchFormatForSetElementOrderList
|
||||||
}
|
}
|
||||||
} else if k != directiveMarker {
|
} else if k != directiveMarker {
|
||||||
fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, k)
|
// recurse for map and slice.
|
||||||
if err != nil {
|
switch typedV := v.(type) {
|
||||||
return nil, err
|
case map[string]interface{}:
|
||||||
}
|
subschema, _, err := schema.LookupPatchMetadataForStruct(k)
|
||||||
_, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
v, err = sortMergeListsByNameMap(typedV, subschema)
|
||||||
|
if err != nil {
|
||||||
// If v is a map or a merge slice, recurse.
|
return nil, err
|
||||||
if typedV, ok := v.(map[string]interface{}); ok {
|
}
|
||||||
var err error
|
case []interface{}:
|
||||||
v, err = sortMergeListsByNameMap(typedV, fieldType)
|
subschema, patchMeta, err := schema.LookupPatchMetadataForSlice(k)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if typedV, ok := v.([]interface{}); ok {
|
|
||||||
if patchStrategy == mergeDirective {
|
if patchStrategy == mergeDirective {
|
||||||
var err error
|
var err error
|
||||||
v, err = sortMergeListsByNameArray(typedV, fieldType.Elem(), fieldPatchMergeKey, true)
|
v, err = sortMergeListsByNameArray(typedV, subschema, patchMeta.GetPatchMergeKey(), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1631,7 +1639,7 @@ func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in an array.
|
// Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in an array.
|
||||||
func sortMergeListsByNameArray(s []interface{}, elemType reflect.Type, mergeKey string, recurse bool) ([]interface{}, error) {
|
func sortMergeListsByNameArray(s []interface{}, schema LookupPatchMeta, mergeKey string, recurse bool) ([]interface{}, error) {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
@ -1654,7 +1662,7 @@ func sortMergeListsByNameArray(s []interface{}, elemType reflect.Type, mergeKey
|
|||||||
for _, elem := range s {
|
for _, elem := range s {
|
||||||
if recurse {
|
if recurse {
|
||||||
typedElem := elem.(map[string]interface{})
|
typedElem := elem.(map[string]interface{})
|
||||||
newElem, err := sortMergeListsByNameMap(typedElem, elemType)
|
newElem, err := sortMergeListsByNameMap(typedElem, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1800,18 +1808,13 @@ func sliceElementType(slices ...[]interface{}) (reflect.Type, error) {
|
|||||||
// objects overlap with different values in any key. All keys are required to be
|
// objects overlap with different values in any key. All keys are required to be
|
||||||
// strings. Since patches of the same Type have congruent keys, this is valid
|
// strings. Since patches of the same Type have congruent keys, this is valid
|
||||||
// for multiple patch types. This method supports strategic merge patch semantics.
|
// for multiple patch types. This method supports strategic merge patch semantics.
|
||||||
func MergingMapsHaveConflicts(left, right map[string]interface{}, dataStruct interface{}) (bool, error) {
|
func MergingMapsHaveConflicts(left, right map[string]interface{}, schema LookupPatchMeta) (bool, error) {
|
||||||
t, err := getTagStructType(dataStruct)
|
return mergingMapFieldsHaveConflicts(left, right, schema, "", "")
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return mergingMapFieldsHaveConflicts(left, right, t, "", "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergingMapFieldsHaveConflicts(
|
func mergingMapFieldsHaveConflicts(
|
||||||
left, right interface{},
|
left, right interface{},
|
||||||
fieldType reflect.Type,
|
schema LookupPatchMeta,
|
||||||
fieldPatchStrategy, fieldPatchMergeKey string,
|
fieldPatchStrategy, fieldPatchMergeKey string,
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
switch leftType := left.(type) {
|
switch leftType := left.(type) {
|
||||||
@ -1842,15 +1845,14 @@ func mergingMapFieldsHaveConflicts(
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
// Check the individual keys.
|
// Check the individual keys.
|
||||||
return mapsHaveConflicts(leftType, rightType, fieldType)
|
return mapsHaveConflicts(leftType, rightType, schema)
|
||||||
|
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
rightType, ok := right.([]interface{})
|
rightType, ok := right.([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
return slicesHaveConflicts(leftType, rightType, fieldType, fieldPatchStrategy, fieldPatchMergeKey)
|
return slicesHaveConflicts(leftType, rightType, schema, fieldPatchStrategy, fieldPatchMergeKey)
|
||||||
|
|
||||||
case string, float64, bool, int, int64, nil:
|
case string, float64, bool, int, int64, nil:
|
||||||
return !reflect.DeepEqual(left, right), nil
|
return !reflect.DeepEqual(left, right), nil
|
||||||
default:
|
default:
|
||||||
@ -1858,21 +1860,37 @@ func mergingMapFieldsHaveConflicts(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapsHaveConflicts(typedLeft, typedRight map[string]interface{}, structType reflect.Type) (bool, error) {
|
func mapsHaveConflicts(typedLeft, typedRight map[string]interface{}, schema LookupPatchMeta) (bool, error) {
|
||||||
for key, leftValue := range typedLeft {
|
for key, leftValue := range typedLeft {
|
||||||
if key != directiveMarker && key != retainKeysDirective {
|
if key != directiveMarker && key != retainKeysDirective {
|
||||||
if rightValue, ok := typedRight[key]; ok {
|
if rightValue, ok := typedRight[key]; ok {
|
||||||
fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(structType, key)
|
var subschema LookupPatchMeta
|
||||||
if err != nil {
|
var patchMeta PatchMeta
|
||||||
return true, err
|
var patchStrategy string
|
||||||
}
|
var err error
|
||||||
_, patchStrategy, err := extractRetainKeysPatchStrategy(fieldPatchStrategies)
|
switch leftValue.(type) {
|
||||||
if err != nil {
|
case []interface{}:
|
||||||
return true, err
|
subschema, patchMeta, err = schema.LookupPatchMetadataForSlice(key)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
_, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.patchStrategies)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
case map[string]interface{}:
|
||||||
|
subschema, patchMeta, err = schema.LookupPatchMetadataForStruct(key)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
_, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.patchStrategies)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue,
|
if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue,
|
||||||
fieldType, patchStrategy, fieldPatchMergeKey); hasConflicts {
|
subschema, patchStrategy, patchMeta.GetPatchMergeKey()); hasConflicts {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1884,7 +1902,7 @@ func mapsHaveConflicts(typedLeft, typedRight map[string]interface{}, structType
|
|||||||
|
|
||||||
func slicesHaveConflicts(
|
func slicesHaveConflicts(
|
||||||
typedLeft, typedRight []interface{},
|
typedLeft, typedRight []interface{},
|
||||||
fieldType reflect.Type,
|
schema LookupPatchMeta,
|
||||||
fieldPatchStrategy, fieldPatchMergeKey string,
|
fieldPatchStrategy, fieldPatchMergeKey string,
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
elementType, err := sliceElementType(typedLeft, typedRight)
|
elementType, err := sliceElementType(typedLeft, typedRight)
|
||||||
@ -1892,7 +1910,6 @@ func slicesHaveConflicts(
|
|||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
valueType := fieldType.Elem()
|
|
||||||
if fieldPatchStrategy == mergeDirective {
|
if fieldPatchStrategy == mergeDirective {
|
||||||
// Merging lists of scalars have no conflicts by definition
|
// Merging lists of scalars have no conflicts by definition
|
||||||
// So we only need to check further if the elements are maps
|
// So we only need to check further if the elements are maps
|
||||||
@ -1911,7 +1928,7 @@ func slicesHaveConflicts(
|
|||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapsOfMapsHaveConflicts(leftMap, rightMap, valueType)
|
return mapsOfMapsHaveConflicts(leftMap, rightMap, schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Either we don't have type information, or these are non-merging lists
|
// Either we don't have type information, or these are non-merging lists
|
||||||
@ -1929,7 +1946,7 @@ func slicesHaveConflicts(
|
|||||||
// Compare the slices element by element in order
|
// Compare the slices element by element in order
|
||||||
// This test will fail if the slices are not sorted
|
// This test will fail if the slices are not sorted
|
||||||
for i := range typedLeft {
|
for i := range typedLeft {
|
||||||
if hasConflicts, err := mergingMapFieldsHaveConflicts(typedLeft[i], typedRight[i], valueType, "", ""); hasConflicts {
|
if hasConflicts, err := mergingMapFieldsHaveConflicts(typedLeft[i], typedRight[i], schema, "", ""); hasConflicts {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1956,10 +1973,10 @@ func sliceOfMapsToMapOfMaps(slice []interface{}, mergeKey string) (map[string]in
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapsOfMapsHaveConflicts(typedLeft, typedRight map[string]interface{}, structType reflect.Type) (bool, error) {
|
func mapsOfMapsHaveConflicts(typedLeft, typedRight map[string]interface{}, schema LookupPatchMeta) (bool, error) {
|
||||||
for key, leftValue := range typedLeft {
|
for key, leftValue := range typedLeft {
|
||||||
if rightValue, ok := typedRight[key]; ok {
|
if rightValue, ok := typedRight[key]; ok {
|
||||||
if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue, structType, "", ""); hasConflicts {
|
if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue, schema, "", ""); hasConflicts {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1979,7 +1996,7 @@ func mapsOfMapsHaveConflicts(typedLeft, typedRight map[string]interface{}, struc
|
|||||||
// 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). We also propagate values fields that do not exist in original but are explicitly
|
// value). We also propagate values fields that do not exist in original but are explicitly
|
||||||
// defined in modified.
|
// defined in modified.
|
||||||
func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct interface{}, overwrite bool, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
|
func CreateThreeWayMergePatch(original, modified, current []byte, schema LookupPatchMeta, overwrite bool, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
|
||||||
originalMap := map[string]interface{}{}
|
originalMap := map[string]interface{}{}
|
||||||
if len(original) > 0 {
|
if len(original) > 0 {
|
||||||
if err := json.Unmarshal(original, &originalMap); err != nil {
|
if err := json.Unmarshal(original, &originalMap); err != nil {
|
||||||
@ -2001,11 +2018,6 @@ func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := getTagStructType(dataStruct)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The patch is the difference from current to modified without deletions, plus deletions
|
// 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
|
// 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
|
// original to modified, and delta, which is the difference from current to modified without
|
||||||
@ -2014,7 +2026,7 @@ func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct int
|
|||||||
IgnoreDeletions: true,
|
IgnoreDeletions: true,
|
||||||
SetElementOrder: true,
|
SetElementOrder: true,
|
||||||
}
|
}
|
||||||
deltaMap, err := diffMaps(currentMap, modifiedMap, t, deltaMapDiffOptions)
|
deltaMap, err := diffMaps(currentMap, modifiedMap, schema, deltaMapDiffOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -2022,13 +2034,13 @@ func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct int
|
|||||||
SetElementOrder: true,
|
SetElementOrder: true,
|
||||||
IgnoreChangesAndAdditions: true,
|
IgnoreChangesAndAdditions: true,
|
||||||
}
|
}
|
||||||
deletionsMap, err := diffMaps(originalMap, modifiedMap, t, deletionsMapDiffOptions)
|
deletionsMap, err := diffMaps(originalMap, modifiedMap, schema, deletionsMapDiffOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeOptions := MergeOptions{}
|
mergeOptions := MergeOptions{}
|
||||||
patchMap, err := mergeMap(deletionsMap, deltaMap, t, mergeOptions)
|
patchMap, err := mergeMap(deletionsMap, deltaMap, schema, mergeOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -2044,12 +2056,12 @@ func CreateThreeWayMergePatch(original, modified, current []byte, dataStruct int
|
|||||||
// then return a conflict error.
|
// then return a conflict error.
|
||||||
if !overwrite {
|
if !overwrite {
|
||||||
changeMapDiffOptions := DiffOptions{}
|
changeMapDiffOptions := DiffOptions{}
|
||||||
changedMap, err := diffMaps(originalMap, currentMap, t, changeMapDiffOptions)
|
changedMap, err := diffMaps(originalMap, currentMap, schema, changeMapDiffOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hasConflicts, err := MergingMapsHaveConflicts(patchMap, changedMap, dataStruct)
|
hasConflicts, err := MergingMapsHaveConflicts(patchMap, changedMap, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -17,17 +17,25 @@ limitations under the License.
|
|||||||
package strategicpatch
|
package strategicpatch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/ghodss/yaml"
|
"github.com/ghodss/yaml"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/json"
|
||||||
"k8s.io/apimachinery/pkg/util/mergepatch"
|
"k8s.io/apimachinery/pkg/util/mergepatch"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
sptest "k8s.io/apimachinery/pkg/util/strategicpatch/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fakeMergeItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-merge-item.json")}
|
||||||
|
fakePrecisionItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-precision-item.json")}
|
||||||
)
|
)
|
||||||
|
|
||||||
type SortMergeListTestCases struct {
|
type SortMergeListTestCases struct {
|
||||||
@ -86,31 +94,34 @@ type StrategicMergePatchRawTestCaseData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MergeItem struct {
|
type MergeItem struct {
|
||||||
Name string
|
Name string `json:"name,omitempty"`
|
||||||
Value string
|
Value string `json:"value,omitempty"`
|
||||||
Other string
|
Other string `json:"other,omitempty"`
|
||||||
MergingList []MergeItem `patchStrategy:"merge" patchMergeKey:"name"`
|
MergingList []MergeItem `json:"mergingList,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
|
||||||
NonMergingList []MergeItem
|
NonMergingList []MergeItem `json:"nonMergingList,omitempty"`
|
||||||
MergingIntList []int `patchStrategy:"merge"`
|
MergingIntList []int `json:"mergingIntList,omitempty" patchStrategy:"merge"`
|
||||||
NonMergingIntList []int
|
NonMergingIntList []int `json:"nonMergingIntList,omitempty"`
|
||||||
MergeItemPtr *MergeItem `patchStrategy:"merge" patchMergeKey:"name"`
|
MergeItemPtr *MergeItem `json:"mergeItemPtr,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
|
||||||
SimpleMap map[string]string
|
SimpleMap map[string]string `json:"simpleMap,omitempty"`
|
||||||
ReplacingItem runtime.RawExtension `patchStrategy:"replace"`
|
ReplacingItem runtime.RawExtension `json:"replacingItem,omitempty" patchStrategy:"replace"`
|
||||||
RetainKeysMap RetainKeysMergeItem `patchStrategy:"retainKeys"`
|
RetainKeysMap RetainKeysMergeItem `json:"retainKeysMap,omitempty" patchStrategy:"retainKeys"`
|
||||||
RetainKeysMergingList []MergeItem `patchStrategy:"merge,retainKeys" patchMergeKey:"name"`
|
RetainKeysMergingList []MergeItem `json:"retainKeysMergingList,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RetainKeysMergeItem struct {
|
type RetainKeysMergeItem struct {
|
||||||
Name string
|
Name string `json:"name,omitempty"`
|
||||||
Value string
|
Value string `json:"value,omitempty"`
|
||||||
Other string
|
Other string `json:"other,omitempty"`
|
||||||
SimpleMap map[string]string
|
SimpleMap map[string]string `json:"simpleMap,omitempty"`
|
||||||
MergingIntList []int `patchStrategy:"merge"`
|
MergingIntList []int `json:"mergingIntList,omitempty" patchStrategy:"merge"`
|
||||||
MergingList []MergeItem `patchStrategy:"merge" patchMergeKey:"name"`
|
MergingList []MergeItem `json:"mergingList,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
|
||||||
NonMergingList []MergeItem
|
NonMergingList []MergeItem `json:"nonMergingList,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var mergeItem MergeItem
|
var (
|
||||||
|
mergeItem MergeItem
|
||||||
|
mergeItemStructSchema = PatchMetaFromStruct{T: GetTagStructTypeOrDie(mergeItem)}
|
||||||
|
)
|
||||||
|
|
||||||
// These are test cases for SortMergeList, used to assert that it (recursively)
|
// These are test cases for SortMergeList, used to assert that it (recursively)
|
||||||
// sorts both merging and non merging lists correctly.
|
// sorts both merging and non merging lists correctly.
|
||||||
@ -151,7 +162,6 @@ testCases:
|
|||||||
- name: 3
|
- name: 3
|
||||||
- name: 2
|
- name: 2
|
||||||
- description: sort lists of maps and nested lists of maps
|
- description: sort lists of maps and nested lists of maps
|
||||||
fieldTypes:
|
|
||||||
original:
|
original:
|
||||||
mergingList:
|
mergingList:
|
||||||
- name: 2
|
- name: 2
|
||||||
@ -271,6 +281,14 @@ testCases:
|
|||||||
`)
|
`)
|
||||||
|
|
||||||
func TestSortMergeLists(t *testing.T) {
|
func TestSortMergeLists(t *testing.T) {
|
||||||
|
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
||||||
|
Schema: sptest.GetSchemaOrDie(fakeMergeItemSchema, "mergeItem"),
|
||||||
|
}
|
||||||
|
schemas := []LookupPatchMeta{
|
||||||
|
mergeItemStructSchema,
|
||||||
|
mergeItemOpenapiSchema,
|
||||||
|
}
|
||||||
|
|
||||||
tc := SortMergeListTestCases{}
|
tc := SortMergeListTestCases{}
|
||||||
err := yaml.Unmarshal(sortMergeListTestCaseData, &tc)
|
err := yaml.Unmarshal(sortMergeListTestCaseData, &tc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -278,12 +296,15 @@ func TestSortMergeLists(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range tc.TestCases {
|
for _, schema := range schemas {
|
||||||
got := sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description)
|
for _, c := range tc.TestCases {
|
||||||
expected := testObjectToJSONOrFail(t, c.Sorted)
|
temp := testObjectToJSONOrFail(t, c.Original)
|
||||||
if !reflect.DeepEqual(got, expected) {
|
got := sortJsonOrFail(t, temp, c.Description, schema)
|
||||||
t.Errorf("error in test case: %s\ncannot sort object:\n%s\nexpected:\n%s\ngot:\n%s\n",
|
expected := testObjectToJSONOrFail(t, c.Sorted)
|
||||||
c.Description, mergepatch.ToYAMLOrError(c.Original), mergepatch.ToYAMLOrError(c.Sorted), jsonToYAMLOrError(got))
|
if !reflect.DeepEqual(got, expected) {
|
||||||
|
t.Errorf("using %s error in test case: %s\ncannot sort object:\n%s\nexpected:\n%s\ngot:\n%s\n",
|
||||||
|
getSchemaType(schema), c.Description, mergepatch.ToYAMLOrError(c.Original), mergepatch.ToYAMLOrError(c.Sorted), jsonToYAMLOrError(got))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -636,6 +657,14 @@ mergingIntList:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCustomStrategicMergePatch(t *testing.T) {
|
func TestCustomStrategicMergePatch(t *testing.T) {
|
||||||
|
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
||||||
|
Schema: sptest.GetSchemaOrDie(fakeMergeItemSchema, "mergeItem"),
|
||||||
|
}
|
||||||
|
schemas := []LookupPatchMeta{
|
||||||
|
mergeItemStructSchema,
|
||||||
|
mergeItemOpenapiSchema,
|
||||||
|
}
|
||||||
|
|
||||||
tc := StrategicMergePatchTestCases{}
|
tc := StrategicMergePatchTestCases{}
|
||||||
err := yaml.Unmarshal(customStrategicMergePatchTestCaseData, &tc)
|
err := yaml.Unmarshal(customStrategicMergePatchTestCaseData, &tc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -643,14 +672,16 @@ func TestCustomStrategicMergePatch(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range tc.TestCases {
|
for _, schema := range schemas {
|
||||||
original, expectedTwoWayPatch, _, expectedResult := twoWayTestCaseToJSONOrFail(t, c)
|
for _, c := range tc.TestCases {
|
||||||
testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, "")
|
original, expectedTwoWayPatch, _, expectedResult := twoWayTestCaseToJSONOrFail(t, c, schema)
|
||||||
}
|
testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, "", schema)
|
||||||
|
}
|
||||||
|
|
||||||
for _, c := range customStrategicMergePatchRawTestCases {
|
for _, c := range customStrategicMergePatchRawTestCases {
|
||||||
original, expectedTwoWayPatch, _, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c)
|
original, expectedTwoWayPatch, _, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c)
|
||||||
testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, c.ExpectedError)
|
testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, c.ExpectedError, schema)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2350,68 +2381,6 @@ mergingList:
|
|||||||
value: 1
|
value: 1
|
||||||
- name: 2
|
- name: 2
|
||||||
other: b
|
other: b
|
||||||
`),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Description: "replace non merging list nested in merging list with value conflict",
|
|
||||||
StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{
|
|
||||||
Original: []byte(`
|
|
||||||
mergingList:
|
|
||||||
- name: 1
|
|
||||||
nonMergingList:
|
|
||||||
- name: 1
|
|
||||||
- name: 2
|
|
||||||
value: 2
|
|
||||||
- name: 2
|
|
||||||
`),
|
|
||||||
TwoWay: []byte(`
|
|
||||||
$setElementOrder/mergingList:
|
|
||||||
- name: 1
|
|
||||||
- name: 2
|
|
||||||
mergingList:
|
|
||||||
- name: 1
|
|
||||||
nonMergingList:
|
|
||||||
- name: 1
|
|
||||||
value: 1
|
|
||||||
`),
|
|
||||||
Modified: []byte(`
|
|
||||||
mergingList:
|
|
||||||
- name: 1
|
|
||||||
nonMergingList:
|
|
||||||
- name: 1
|
|
||||||
value: 1
|
|
||||||
- name: 2
|
|
||||||
`),
|
|
||||||
Current: []byte(`
|
|
||||||
mergingList:
|
|
||||||
- name: 1
|
|
||||||
other: a
|
|
||||||
nonMergingList:
|
|
||||||
- name: 1
|
|
||||||
value: c
|
|
||||||
- name: 2
|
|
||||||
other: b
|
|
||||||
`),
|
|
||||||
ThreeWay: []byte(`
|
|
||||||
$setElementOrder/mergingList:
|
|
||||||
- name: 1
|
|
||||||
- name: 2
|
|
||||||
mergingList:
|
|
||||||
- name: 1
|
|
||||||
nonMergingList:
|
|
||||||
- name: 1
|
|
||||||
value: 1
|
|
||||||
`),
|
|
||||||
Result: []byte(`
|
|
||||||
mergingList:
|
|
||||||
- name: 1
|
|
||||||
other: a
|
|
||||||
nonMergingList:
|
|
||||||
- name: 1
|
|
||||||
value: 1
|
|
||||||
- name: 2
|
|
||||||
other: b
|
|
||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -6041,14 +6010,16 @@ mergeItemPtr:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStrategicMergePatch(t *testing.T) {
|
func TestStrategicMergePatch(t *testing.T) {
|
||||||
testStrategicMergePatchWithCustomArguments(t, "bad original",
|
testStrategicMergePatchWithCustomArgumentsUsingStruct(t, "bad struct",
|
||||||
"<THIS IS NOT JSON>", "{}", mergeItem, mergepatch.ErrBadJSONDoc)
|
|
||||||
testStrategicMergePatchWithCustomArguments(t, "bad patch",
|
|
||||||
"{}", "<THIS IS NOT JSON>", mergeItem, mergepatch.ErrBadJSONDoc)
|
|
||||||
testStrategicMergePatchWithCustomArguments(t, "bad struct",
|
|
||||||
"{}", "{}", []byte("<THIS IS NOT A STRUCT>"), mergepatch.ErrBadArgKind(struct{}{}, []byte{}))
|
"{}", "{}", []byte("<THIS IS NOT A STRUCT>"), mergepatch.ErrBadArgKind(struct{}{}, []byte{}))
|
||||||
testStrategicMergePatchWithCustomArguments(t, "nil struct",
|
|
||||||
"{}", "{}", nil, mergepatch.ErrBadArgKind(struct{}{}, nil))
|
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
||||||
|
Schema: sptest.GetSchemaOrDie(fakeMergeItemSchema, "mergeItem"),
|
||||||
|
}
|
||||||
|
schemas := []LookupPatchMeta{
|
||||||
|
mergeItemStructSchema,
|
||||||
|
mergeItemOpenapiSchema,
|
||||||
|
}
|
||||||
|
|
||||||
tc := StrategicMergePatchTestCases{}
|
tc := StrategicMergePatchTestCases{}
|
||||||
err := yaml.Unmarshal(createStrategicMergePatchTestCaseData, &tc)
|
err := yaml.Unmarshal(createStrategicMergePatchTestCaseData, &tc)
|
||||||
@ -6057,53 +6028,76 @@ func TestStrategicMergePatch(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range tc.TestCases {
|
for _, schema := range schemas {
|
||||||
testTwoWayPatch(t, c)
|
testStrategicMergePatchWithCustomArguments(t, "bad original",
|
||||||
testThreeWayPatch(t, c)
|
"<THIS IS NOT JSON>", "{}", schema, mergepatch.ErrBadJSONDoc)
|
||||||
}
|
testStrategicMergePatchWithCustomArguments(t, "bad patch",
|
||||||
|
"{}", "<THIS IS NOT JSON>", schema, mergepatch.ErrBadJSONDoc)
|
||||||
|
testStrategicMergePatchWithCustomArguments(t, "nil struct",
|
||||||
|
"{}", "{}", nil, mergepatch.ErrBadArgKind(struct{}{}, nil))
|
||||||
|
|
||||||
// run multiple times to exercise different map traversal orders
|
for _, c := range tc.TestCases {
|
||||||
for i := 0; i < 10; i++ {
|
testTwoWayPatch(t, c, schema)
|
||||||
for _, c := range strategicMergePatchRawTestCases {
|
testThreeWayPatch(t, c, schema)
|
||||||
testTwoWayPatchForRawTestCase(t, c)
|
}
|
||||||
testThreeWayPatchForRawTestCase(t, c)
|
|
||||||
|
// run multiple times to exercise different map traversal orders
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
for _, c := range strategicMergePatchRawTestCases {
|
||||||
|
testTwoWayPatchForRawTestCase(t, c, schema)
|
||||||
|
testThreeWayPatchForRawTestCase(t, c, schema)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStrategicMergePatchWithCustomArguments(t *testing.T, description, original, patch string, dataStruct interface{}, err error) {
|
func testStrategicMergePatchWithCustomArgumentsUsingStruct(t *testing.T, description, original, patch string, dataStruct interface{}, expected error) {
|
||||||
_, err2 := StrategicMergePatch([]byte(original), []byte(patch), dataStruct)
|
schema, actual := NewPatchMetaFromStruct(dataStruct)
|
||||||
if err2 != err {
|
// If actual is not nil, check error. If errors match, return.
|
||||||
if err2 == nil {
|
if actual != nil {
|
||||||
t.Errorf("expected error: %s\ndid not occur in test case: %s", err, description)
|
checkErrorsEqual(t, description, expected, actual, schema)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
testStrategicMergePatchWithCustomArguments(t, description, original, patch, schema, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testStrategicMergePatchWithCustomArguments(t *testing.T, description, original, patch string, schema LookupPatchMeta, expected error) {
|
||||||
|
_, actual := StrategicMergePatch([]byte(original), []byte(patch), schema)
|
||||||
|
checkErrorsEqual(t, description, expected, actual, schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkErrorsEqual(t *testing.T, description string, expected, actual error, schema LookupPatchMeta) {
|
||||||
|
if actual != expected {
|
||||||
|
if actual == nil {
|
||||||
|
t.Errorf("using %s expected error: %s\ndid not occur in test case: %s", getSchemaType(schema), expected, description)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil || err2.Error() != err.Error() {
|
if expected == nil || actual.Error() != expected.Error() {
|
||||||
t.Errorf("unexpected error: %s\noccurred in test case: %s", err2, description)
|
t.Errorf("using %s unexpected error: %s\noccurred in test case: %s", getSchemaType(schema), actual, description)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTwoWayPatch(t *testing.T, c StrategicMergePatchTestCase) {
|
func testTwoWayPatch(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) {
|
||||||
original, expectedPatch, modified, expectedResult := twoWayTestCaseToJSONOrFail(t, c)
|
original, expectedPatch, modified, expectedResult := twoWayTestCaseToJSONOrFail(t, c, schema)
|
||||||
|
|
||||||
actualPatch, err := CreateTwoWayMergePatch(original, modified, mergeItem)
|
actualPatch, err := CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error: %s\nin test case: %s\ncannot create two way patch: %s:\n%s\n",
|
t.Errorf("using %s error: %s\nin test case: %s\ncannot create two way patch: %s:\n%s\n",
|
||||||
err, c.Description, original, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
getSchemaType(schema), err, c.Description, original, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
testPatchCreation(t, expectedPatch, actualPatch, c.Description)
|
testPatchCreation(t, expectedPatch, actualPatch, c.Description)
|
||||||
testPatchApplication(t, original, actualPatch, expectedResult, c.Description, "")
|
testPatchApplication(t, original, actualPatch, expectedResult, c.Description, "", schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase) {
|
func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase, schema LookupPatchMeta) {
|
||||||
original, expectedPatch, modified, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c)
|
original, expectedPatch, modified, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c)
|
||||||
|
|
||||||
actualPatch, err := CreateTwoWayMergePatch(original, modified, mergeItem)
|
actualPatch, err := CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error: %s\nin test case: %s\ncannot create two way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n",
|
t.Errorf("error: %s\nin test case: %s\ncannot create two way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n",
|
||||||
err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result)
|
err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result)
|
||||||
@ -6111,18 +6105,18 @@ func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCas
|
|||||||
}
|
}
|
||||||
|
|
||||||
testPatchCreation(t, expectedPatch, actualPatch, c.Description)
|
testPatchCreation(t, expectedPatch, actualPatch, c.Description)
|
||||||
testPatchApplication(t, original, actualPatch, expectedResult, c.Description, c.ExpectedError)
|
testPatchApplication(t, original, actualPatch, expectedResult, c.Description, c.ExpectedError, schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
func twoWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase) ([]byte, []byte, []byte, []byte) {
|
func twoWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) ([]byte, []byte, []byte, []byte) {
|
||||||
expectedResult := c.TwoWayResult
|
expectedResult := c.TwoWayResult
|
||||||
if expectedResult == nil {
|
if expectedResult == nil {
|
||||||
expectedResult = c.Modified
|
expectedResult = c.Modified
|
||||||
}
|
}
|
||||||
return sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description),
|
return sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description, schema),
|
||||||
sortJsonOrFail(t, testObjectToJSONOrFail(t, c.TwoWay), c.Description),
|
sortJsonOrFail(t, testObjectToJSONOrFail(t, c.TwoWay), c.Description, schema),
|
||||||
sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Modified), c.Description),
|
sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Modified), c.Description, schema),
|
||||||
sortJsonOrFail(t, testObjectToJSONOrFail(t, expectedResult), c.Description)
|
sortJsonOrFail(t, testObjectToJSONOrFail(t, expectedResult), c.Description, schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
func twoWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCase) ([]byte, []byte, []byte, []byte) {
|
func twoWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCase) ([]byte, []byte, []byte, []byte) {
|
||||||
@ -6136,94 +6130,94 @@ func twoWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCas
|
|||||||
yamlToJSONOrError(t, expectedResult)
|
yamlToJSONOrError(t, expectedResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase) {
|
func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) {
|
||||||
original, modified, current, expected, result := threeWayTestCaseToJSONOrFail(t, c)
|
original, modified, current, expected, result := threeWayTestCaseToJSONOrFail(t, c, schema)
|
||||||
actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, false)
|
actual, err := CreateThreeWayMergePatch(original, modified, current, schema, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !mergepatch.IsConflict(err) {
|
if !mergepatch.IsConflict(err) {
|
||||||
t.Errorf("error: %s\nin test case: %s\ncannot create three way patch:\n%s\n",
|
t.Errorf("using %s error: %s\nin test case: %s\ncannot create three way patch:\n%s\n",
|
||||||
err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
getSchemaType(schema), err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(c.Description, "conflict") {
|
if !strings.Contains(c.Description, "conflict") {
|
||||||
t.Errorf("unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\n%s\n",
|
t.Errorf("using %s unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\n%s\n",
|
||||||
err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
getSchemaType(schema), err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.Result) > 0 {
|
if len(c.Result) > 0 {
|
||||||
actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, true)
|
actual, err := CreateThreeWayMergePatch(original, modified, current, schema, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error: %s\nin test case: %s\ncannot force three way patch application:\n%s\n",
|
t.Errorf("using %s error: %s\nin test case: %s\ncannot force three way patch application:\n%s\n",
|
||||||
err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
getSchemaType(schema), err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
testPatchCreation(t, expected, actual, c.Description)
|
testPatchCreation(t, expected, actual, c.Description)
|
||||||
testPatchApplication(t, current, actual, result, c.Description, "")
|
testPatchApplication(t, current, actual, result, c.Description, "", schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(c.Description, "conflict") || len(c.Result) < 1 {
|
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",
|
t.Errorf("using %s error in test case: %s\nexpected conflict did not occur:\n%s\n",
|
||||||
c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
getSchemaType(schema), c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
testPatchCreation(t, expected, actual, c.Description)
|
testPatchCreation(t, expected, actual, c.Description)
|
||||||
testPatchApplication(t, current, actual, result, c.Description, "")
|
testPatchApplication(t, current, actual, result, c.Description, "", schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testThreeWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase) {
|
func testThreeWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase, schema LookupPatchMeta) {
|
||||||
original, modified, current, expected, result := threeWayRawTestCaseToJSONOrFail(t, c)
|
original, modified, current, expected, result := threeWayRawTestCaseToJSONOrFail(t, c)
|
||||||
actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, false)
|
actual, err := CreateThreeWayMergePatch(original, modified, current, schema, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !mergepatch.IsConflict(err) {
|
if !mergepatch.IsConflict(err) {
|
||||||
t.Errorf("error: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n",
|
t.Errorf("using %s error: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n",
|
||||||
err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result)
|
getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(c.Description, "conflict") {
|
if !strings.Contains(c.Description, "conflict") {
|
||||||
t.Errorf("unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n",
|
t.Errorf("using %s unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n",
|
||||||
err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result)
|
getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.Result) > 0 {
|
if len(c.Result) > 0 {
|
||||||
actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, true)
|
actual, err := CreateThreeWayMergePatch(original, modified, current, schema, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error: %s\nin test case: %s\ncannot force three way patch application:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n",
|
t.Errorf("using %s error: %s\nin test case: %s\ncannot force three way patch application:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n",
|
||||||
err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result)
|
getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
testPatchCreation(t, expected, actual, c.Description)
|
testPatchCreation(t, expected, actual, c.Description)
|
||||||
testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError)
|
testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError, schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(c.Description, "conflict") || len(c.Result) < 1 {
|
if strings.Contains(c.Description, "conflict") || len(c.Result) < 1 {
|
||||||
t.Errorf("error: %s\nin test case: %s\nexpected conflict did not occur:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n",
|
t.Errorf("using %s error: %s\nin test case: %s\nexpected conflict did not occur:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n",
|
||||||
err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result)
|
getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
testPatchCreation(t, expected, actual, c.Description)
|
testPatchCreation(t, expected, actual, c.Description)
|
||||||
testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError)
|
testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError, schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
func threeWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase) ([]byte, []byte, []byte, []byte, []byte) {
|
func threeWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) ([]byte, []byte, []byte, []byte, []byte) {
|
||||||
return sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description),
|
return sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description, schema),
|
||||||
sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Modified), c.Description),
|
sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Modified), c.Description, schema),
|
||||||
sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Current), c.Description),
|
sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Current), c.Description, schema),
|
||||||
sortJsonOrFail(t, testObjectToJSONOrFail(t, c.ThreeWay), c.Description),
|
sortJsonOrFail(t, testObjectToJSONOrFail(t, c.ThreeWay), c.Description, schema),
|
||||||
sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Result), c.Description)
|
sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Result), c.Description, schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
func threeWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCase) ([]byte, []byte, []byte, []byte, []byte) {
|
func threeWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCase) ([]byte, []byte, []byte, []byte, []byte) {
|
||||||
@ -6242,22 +6236,22 @@ func testPatchCreation(t *testing.T, expected, actual []byte, description string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPatchApplication(t *testing.T, original, patch, expected []byte, description, expectedError string) {
|
func testPatchApplication(t *testing.T, original, patch, expected []byte, description, expectedError string, schema LookupPatchMeta) {
|
||||||
result, err := StrategicMergePatch(original, patch, mergeItem)
|
result, err := StrategicMergePatchUsingLookupPatchMeta(original, patch, schema)
|
||||||
if len(expectedError) != 0 {
|
if len(expectedError) != 0 {
|
||||||
if err != nil && strings.Contains(err.Error(), expectedError) {
|
if err != nil && strings.Contains(err.Error(), expectedError) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.Errorf("expected error should contain:\n%s\nin test case: %s\nbut got:\n%s\n", expectedError, description, err)
|
t.Errorf("using %s expected error should contain:\n%s\nin test case: %s\nbut got:\n%s\n", getSchemaType(schema), expectedError, description, err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n",
|
t.Errorf("using %s error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n",
|
||||||
err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original))
|
getSchemaType(schema), err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(result, expected) {
|
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"
|
format := "using 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,
|
t.Errorf(format, description,
|
||||||
jsonToYAMLOrError(original), jsonToYAMLOrError(patch),
|
jsonToYAMLOrError(original), jsonToYAMLOrError(patch),
|
||||||
jsonToYAMLOrError(expected), jsonToYAMLOrError(result))
|
jsonToYAMLOrError(expected), jsonToYAMLOrError(result))
|
||||||
@ -6277,19 +6271,23 @@ func testObjectToJSONOrFail(t *testing.T, o map[string]interface{}) []byte {
|
|||||||
return j
|
return j
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortJsonOrFail(t *testing.T, j []byte, description string) []byte {
|
func sortJsonOrFail(t *testing.T, j []byte, description string, schema LookupPatchMeta) []byte {
|
||||||
if j == nil {
|
if j == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
r, err := sortMergeListsByName(j, mergeItem)
|
r, err := sortMergeListsByName(j, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error: %s\nin test case: %s\ncannot sort object:\n%s\n", err, description, j)
|
t.Errorf("using %s error: %s\n in test case: %s\ncannot sort object:\n%s\n", getSchemaType(schema), err, description, j)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSchemaType(schema LookupPatchMeta) string {
|
||||||
|
return reflect.TypeOf(schema).String()
|
||||||
|
}
|
||||||
|
|
||||||
func jsonToYAMLOrError(j []byte) string {
|
func jsonToYAMLOrError(j []byte) string {
|
||||||
y, err := jsonToYAML(j)
|
y, err := jsonToYAML(j)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -6336,14 +6334,17 @@ func yamlToJSONOrError(t *testing.T, y []byte) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PrecisionItem struct {
|
type PrecisionItem struct {
|
||||||
Name string
|
Name string `json:"name,omitempty"`
|
||||||
Int32 int32
|
Int32 int32 `json:"int32,omitempty"`
|
||||||
Int64 int64
|
Int64 int64 `json:"int64,omitempty"`
|
||||||
Float32 float32
|
Float32 float32 `json:"float32,omitempty"`
|
||||||
Float64 float64
|
Float64 float64 `json:"float64,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var precisionItem PrecisionItem
|
var (
|
||||||
|
precisionItem PrecisionItem
|
||||||
|
precisionItemStructSchema = PatchMetaFromStruct{T: GetTagStructTypeOrDie(precisionItem)}
|
||||||
|
)
|
||||||
|
|
||||||
func TestNumberConversion(t *testing.T) {
|
func TestNumberConversion(t *testing.T) {
|
||||||
testcases := map[string]struct {
|
testcases := map[string]struct {
|
||||||
@ -6396,25 +6397,35 @@ func TestNumberConversion(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, tc := range testcases {
|
precisionItemOpenapiSchema := PatchMetaFromOpenAPI{
|
||||||
patch, err := CreateTwoWayMergePatch([]byte(tc.Old), []byte(tc.New), precisionItem)
|
Schema: sptest.GetSchemaOrDie(fakePrecisionItemSchema, "precisionItem"),
|
||||||
if err != nil {
|
}
|
||||||
t.Errorf("%s: unexpected error %v", k, err)
|
precisionItemSchemas := []LookupPatchMeta{
|
||||||
continue
|
precisionItemStructSchema,
|
||||||
}
|
precisionItemOpenapiSchema,
|
||||||
if tc.ExpectedPatch != string(patch) {
|
}
|
||||||
t.Errorf("%s: expected %s, got %s", k, tc.ExpectedPatch, string(patch))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := StrategicMergePatch([]byte(tc.Old), patch, precisionItem)
|
for _, schema := range precisionItemSchemas {
|
||||||
if err != nil {
|
for k, tc := range testcases {
|
||||||
t.Errorf("%s: unexpected error %v", k, err)
|
patch, err := CreateTwoWayMergePatchUsingLookupPatchMeta([]byte(tc.Old), []byte(tc.New), schema)
|
||||||
continue
|
if err != nil {
|
||||||
}
|
t.Errorf("using %s in testcase %s: unexpected error %v", getSchemaType(schema), k, err)
|
||||||
if tc.ExpectedResult != string(result) {
|
continue
|
||||||
t.Errorf("%s: expected %s, got %s", k, tc.ExpectedResult, string(result))
|
}
|
||||||
continue
|
if tc.ExpectedPatch != string(patch) {
|
||||||
|
t.Errorf("using %s in testcase %s: expected %s, got %s", getSchemaType(schema), k, tc.ExpectedPatch, string(patch))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := StrategicMergePatchUsingLookupPatchMeta([]byte(tc.Old), patch, schema)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("using %s in testcase %s: unexpected error %v", getSchemaType(schema), k, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tc.ExpectedResult != string(result) {
|
||||||
|
t.Errorf("using %s in testcase %s: expected %s, got %s", getSchemaType(schema), k, tc.ExpectedResult, string(result))
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6437,7 +6448,7 @@ replacingItem:
|
|||||||
name: my-object
|
name: my-object
|
||||||
value: some-value
|
value: some-value
|
||||||
other: current-other
|
other: current-other
|
||||||
merginglist:
|
mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
- name: 2
|
- name: 2
|
||||||
- name: 3
|
- name: 3
|
||||||
@ -6451,7 +6462,7 @@ replacingItem:
|
|||||||
name: my-object
|
name: my-object
|
||||||
value: some-value
|
value: some-value
|
||||||
other: current-other
|
other: current-other
|
||||||
merginglist:
|
mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
- name: 2
|
- name: 2
|
||||||
- name: 3
|
- name: 3
|
||||||
@ -6461,7 +6472,7 @@ replacingItem:
|
|||||||
The: RawExtension
|
The: RawExtension
|
||||||
`),
|
`),
|
||||||
TwoWay: []byte(`
|
TwoWay: []byte(`
|
||||||
merginglist:
|
mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
- name: 2
|
- name: 2
|
||||||
- name: 3
|
- name: 3
|
||||||
@ -6474,7 +6485,7 @@ replacingItem:
|
|||||||
name: my-object
|
name: my-object
|
||||||
value: some-value
|
value: some-value
|
||||||
other: current-other
|
other: current-other
|
||||||
merginglist:
|
mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
- name: 2
|
- name: 2
|
||||||
- name: 3
|
- name: 3
|
||||||
@ -6493,7 +6504,7 @@ replacingItem:
|
|||||||
name: my-object
|
name: my-object
|
||||||
value: some-value
|
value: some-value
|
||||||
other: current-other
|
other: current-other
|
||||||
merginglist:
|
mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
- name: 2
|
- name: 2
|
||||||
- name: 3
|
- name: 3
|
||||||
@ -6511,7 +6522,7 @@ replacingItem:
|
|||||||
name: my-object
|
name: my-object
|
||||||
value: some-value
|
value: some-value
|
||||||
other: current-other
|
other: current-other
|
||||||
merginglist:
|
mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
replacingItem:
|
replacingItem:
|
||||||
Some: Generic
|
Some: Generic
|
||||||
@ -6523,7 +6534,7 @@ replacingItem:
|
|||||||
name: my-object
|
name: my-object
|
||||||
value: some-value
|
value: some-value
|
||||||
other: current-other
|
other: current-other
|
||||||
merginglist:
|
mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
- name: 3
|
- name: 3
|
||||||
replacingItem:
|
replacingItem:
|
||||||
@ -6536,7 +6547,7 @@ replacingItem:
|
|||||||
name: my-object
|
name: my-object
|
||||||
value: some-value
|
value: some-value
|
||||||
other: current-other
|
other: current-other
|
||||||
merginglist:
|
mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
- name: 2
|
- name: 2
|
||||||
replacingItem:
|
replacingItem:
|
||||||
@ -6545,10 +6556,10 @@ replacingItem:
|
|||||||
The: RawExtension
|
The: RawExtension
|
||||||
`),
|
`),
|
||||||
TwoWay: []byte(`
|
TwoWay: []byte(`
|
||||||
$setElementOrder/merginglist:
|
$setElementOrder/mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
- name: 2
|
- name: 2
|
||||||
merginglist:
|
mergingList:
|
||||||
- name: 2
|
- name: 2
|
||||||
replacingItem:
|
replacingItem:
|
||||||
Newly: Modified
|
Newly: Modified
|
||||||
@ -6559,7 +6570,7 @@ replacingItem:
|
|||||||
name: my-object
|
name: my-object
|
||||||
value: some-value
|
value: some-value
|
||||||
other: current-other
|
other: current-other
|
||||||
merginglist:
|
mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
- name: 2
|
- name: 2
|
||||||
replacingItem:
|
replacingItem:
|
||||||
@ -6568,10 +6579,10 @@ replacingItem:
|
|||||||
The: RawExtension
|
The: RawExtension
|
||||||
`),
|
`),
|
||||||
ThreeWay: []byte(`
|
ThreeWay: []byte(`
|
||||||
$setElementOrder/merginglist:
|
$setElementOrder/mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
- name: 2
|
- name: 2
|
||||||
merginglist:
|
mergingList:
|
||||||
- name: 2
|
- name: 2
|
||||||
replacingItem:
|
replacingItem:
|
||||||
Newly: Modified
|
Newly: Modified
|
||||||
@ -6582,7 +6593,7 @@ replacingItem:
|
|||||||
name: my-object
|
name: my-object
|
||||||
value: some-value
|
value: some-value
|
||||||
other: current-other
|
other: current-other
|
||||||
merginglist:
|
mergingList:
|
||||||
- name: 1
|
- name: 1
|
||||||
- name: 2
|
- name: 2
|
||||||
- name: 3
|
- name: 3
|
||||||
@ -6596,9 +6607,19 @@ replacingItem:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReplaceWithRawExtension(t *testing.T) {
|
func TestReplaceWithRawExtension(t *testing.T) {
|
||||||
for _, c := range replaceRawExtensionPatchTestCases {
|
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
||||||
testTwoWayPatchForRawTestCase(t, c)
|
Schema: sptest.GetSchemaOrDie(fakeMergeItemSchema, "mergeItem"),
|
||||||
testThreeWayPatchForRawTestCase(t, c)
|
}
|
||||||
|
schemas := []LookupPatchMeta{
|
||||||
|
mergeItemStructSchema,
|
||||||
|
mergeItemOpenapiSchema,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, schema := range schemas {
|
||||||
|
for _, c := range replaceRawExtensionPatchTestCases {
|
||||||
|
testTwoWayPatchForRawTestCase(t, c, schema)
|
||||||
|
testThreeWayPatchForRawTestCase(t, c, schema)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6658,60 +6679,70 @@ func TestUnknownField(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
||||||
|
Schema: sptest.GetSchemaOrDie(fakeMergeItemSchema, "mergeItem"),
|
||||||
|
}
|
||||||
|
schemas := []LookupPatchMeta{
|
||||||
|
mergeItemStructSchema,
|
||||||
|
mergeItemOpenapiSchema,
|
||||||
|
}
|
||||||
|
|
||||||
for _, k := range sets.StringKeySet(testcases).List() {
|
for _, k := range sets.StringKeySet(testcases).List() {
|
||||||
tc := testcases[k]
|
tc := testcases[k]
|
||||||
func() {
|
for _, schema := range schemas {
|
||||||
twoWay, err := CreateTwoWayMergePatch([]byte(tc.Original), []byte(tc.Modified), &MergeItem{})
|
func() {
|
||||||
if err != nil {
|
twoWay, err := CreateTwoWayMergePatchUsingLookupPatchMeta([]byte(tc.Original), []byte(tc.Modified), schema)
|
||||||
if len(tc.ExpectedTwoWayErr) == 0 {
|
if err != nil {
|
||||||
t.Errorf("%s: error making two-way patch: %v", k, err)
|
if len(tc.ExpectedTwoWayErr) == 0 {
|
||||||
|
t.Errorf("using %s in testcase %s: error making two-way patch: %v", getSchemaType(schema), k, err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tc.ExpectedTwoWayErr) {
|
||||||
|
t.Errorf("using %s in testcase %s: expected error making two-way patch to contain '%s', got %s", getSchemaType(schema), k, tc.ExpectedTwoWayErr, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), tc.ExpectedTwoWayErr) {
|
|
||||||
t.Errorf("%s: expected error making two-way patch to contain '%s', got %s", k, tc.ExpectedTwoWayErr, err)
|
if string(twoWay) != tc.ExpectedTwoWay {
|
||||||
|
t.Errorf("using %s in testcase %s: expected two-way patch:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedTwoWay), string(twoWay))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(twoWay) != tc.ExpectedTwoWay {
|
twoWayResult, err := StrategicMergePatchUsingLookupPatchMeta([]byte(tc.Original), twoWay, schema)
|
||||||
t.Errorf("%s: expected two-way patch:\n\t%s\ngot\n\t%s", k, string(tc.ExpectedTwoWay), string(twoWay))
|
if err != nil {
|
||||||
return
|
t.Errorf("using %s in testcase %s: error applying two-way patch: %v", getSchemaType(schema), k, err)
|
||||||
}
|
return
|
||||||
|
|
||||||
twoWayResult, err := StrategicMergePatch([]byte(tc.Original), twoWay, MergeItem{})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: error applying two-way patch: %v", k, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if string(twoWayResult) != tc.ExpectedTwoWayResult {
|
|
||||||
t.Errorf("%s: expected two-way result:\n\t%s\ngot\n\t%s", k, string(tc.ExpectedTwoWayResult), string(twoWayResult))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
func() {
|
|
||||||
threeWay, err := CreateThreeWayMergePatch([]byte(tc.Original), []byte(tc.Modified), []byte(tc.Current), &MergeItem{}, false)
|
|
||||||
if err != nil {
|
|
||||||
if len(tc.ExpectedThreeWayErr) == 0 {
|
|
||||||
t.Errorf("%s: error making three-way patch: %v", k, err)
|
|
||||||
} else if !strings.Contains(err.Error(), tc.ExpectedThreeWayErr) {
|
|
||||||
t.Errorf("%s: expected error making three-way patch to contain '%s', got %s", k, tc.ExpectedThreeWayErr, err)
|
|
||||||
}
|
}
|
||||||
return
|
if string(twoWayResult) != tc.ExpectedTwoWayResult {
|
||||||
}
|
t.Errorf("using %s in testcase %s: expected two-way result:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedTwoWayResult), string(twoWayResult))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if string(threeWay) != tc.ExpectedThreeWay {
|
func() {
|
||||||
t.Errorf("%s: expected three-way patch:\n\t%s\ngot\n\t%s", k, string(tc.ExpectedThreeWay), string(threeWay))
|
threeWay, err := CreateThreeWayMergePatch([]byte(tc.Original), []byte(tc.Modified), []byte(tc.Current), schema, false)
|
||||||
return
|
if err != nil {
|
||||||
}
|
if len(tc.ExpectedThreeWayErr) == 0 {
|
||||||
|
t.Errorf("using %s in testcase %s: error making three-way patch: %v", getSchemaType(schema), k, err)
|
||||||
|
} else if !strings.Contains(err.Error(), tc.ExpectedThreeWayErr) {
|
||||||
|
t.Errorf("using %s in testcase %s: expected error making three-way patch to contain '%s', got %s", getSchemaType(schema), k, tc.ExpectedThreeWayErr, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
threeWayResult, err := StrategicMergePatch([]byte(tc.Current), threeWay, MergeItem{})
|
if string(threeWay) != tc.ExpectedThreeWay {
|
||||||
if err != nil {
|
t.Errorf("using %s in testcase %s: expected three-way patch:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedThreeWay), string(threeWay))
|
||||||
t.Errorf("%s: error applying three-way patch: %v", k, err)
|
return
|
||||||
return
|
}
|
||||||
} else if string(threeWayResult) != tc.ExpectedThreeWayResult {
|
|
||||||
t.Errorf("%s: expected three-way result:\n\t%s\ngot\n\t%s", k, string(tc.ExpectedThreeWayResult), string(threeWayResult))
|
threeWayResult, err := StrategicMergePatch([]byte(tc.Current), threeWay, schema)
|
||||||
return
|
if err != nil {
|
||||||
}
|
t.Errorf("using %s in testcase %s: error applying three-way patch: %v", getSchemaType(schema), k, err)
|
||||||
}()
|
return
|
||||||
|
} else if string(threeWayResult) != tc.ExpectedThreeWayResult {
|
||||||
|
t.Errorf("using %s in testcase %s: expected three-way result:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedThreeWayResult), string(threeWayResult))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
170
staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item.json
vendored
Normal file
170
staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item.json
vendored
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"title": "StrategicMergePatchTestingMergeItem",
|
||||||
|
"version": "v1.9.0"
|
||||||
|
},
|
||||||
|
"paths": {},
|
||||||
|
"definitions": {
|
||||||
|
"mergeItem": {
|
||||||
|
"description": "MergeItem is type definition for testing strategic merge.",
|
||||||
|
"required": [],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "Name field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"description": "Value field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"other": {
|
||||||
|
"description": "Other field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mergingList": {
|
||||||
|
"description": "MergingList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/mergeItem"
|
||||||
|
},
|
||||||
|
"x-kubernetes-patch-merge-key": "name",
|
||||||
|
"x-kubernetes-patch-strategy": "merge"
|
||||||
|
},
|
||||||
|
"nonMergingList": {
|
||||||
|
"description": "NonMergingList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/mergeItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mergingIntList": {
|
||||||
|
"description": "MergingIntList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"x-kubernetes-patch-strategy": "merge"
|
||||||
|
},
|
||||||
|
"nonMergingIntList": {
|
||||||
|
"description": "NonMergingIntList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mergeItemPtr": {
|
||||||
|
"description": "MergeItemPtr field.",
|
||||||
|
"$ref": "#/definitions/mergeItem",
|
||||||
|
"x-kubernetes-patch-merge-key": "name",
|
||||||
|
"x-kubernetes-patch-strategy": "merge"
|
||||||
|
},
|
||||||
|
"simpleMap": {
|
||||||
|
"description": "SimpleMap field.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"replacingItem": {
|
||||||
|
"description": "ReplacingItem field.",
|
||||||
|
"$ref": "#/definitions/io.k8s.apimachinery.pkg.runtime.RawExtension",
|
||||||
|
"x-kubernetes-patch-strategy": "replace"
|
||||||
|
},
|
||||||
|
"retainKeysMap": {
|
||||||
|
"description": "RetainKeysMap field.",
|
||||||
|
"$ref": "#/definitions/retainKeysMergeItem",
|
||||||
|
"x-kubernetes-patch-strategy": "retainKeys"
|
||||||
|
},
|
||||||
|
"retainKeysMergingList": {
|
||||||
|
"description": "RetainKeysMergingList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/mergeItem"
|
||||||
|
},
|
||||||
|
"x-kubernetes-patch-merge-key": "name",
|
||||||
|
"x-kubernetes-patch-strategy": "merge,retainKeys"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-kubernetes-group-version-kind": [
|
||||||
|
{
|
||||||
|
"group": "fake-group",
|
||||||
|
"kind": "mergeItem",
|
||||||
|
"version": "some-version"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"retainKeysMergeItem": {
|
||||||
|
"description": "RetainKeysMergeItem is type definition for testing strategic merge.",
|
||||||
|
"required": [],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "Name field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"description": "Value field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"other": {
|
||||||
|
"description": "Other field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"simpleMap": {
|
||||||
|
"description": "SimpleMap field.",
|
||||||
|
"additionalProperties": "object",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mergingList": {
|
||||||
|
"description": "MergingList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/mergeItem"
|
||||||
|
},
|
||||||
|
"x-kubernetes-patch-merge-key": "name",
|
||||||
|
"x-kubernetes-patch-strategy": "merge"
|
||||||
|
},
|
||||||
|
"nonMergingList": {
|
||||||
|
"description": "NonMergingList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/mergeItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mergingIntList": {
|
||||||
|
"description": "MergingIntList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"x-kubernetes-patch-strategy": "merge"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-kubernetes-group-version-kind": [
|
||||||
|
{
|
||||||
|
"group": "fake-group",
|
||||||
|
"kind": "retainKeysMergeItem",
|
||||||
|
"version": "some-version"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"io.k8s.apimachinery.pkg.runtime.RawExtension": {
|
||||||
|
"description": "RawExtension is used to hold extensions in external versions.",
|
||||||
|
"required": [
|
||||||
|
"Raw"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Raw": {
|
||||||
|
"description": "Raw is the underlying serialization of this object.",
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item.json
vendored
Normal file
47
staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item.json
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"title": "StrategicMergePatchTestingPrecisionItem",
|
||||||
|
"version": "v1.9.0"
|
||||||
|
},
|
||||||
|
"paths": {},
|
||||||
|
"definitions": {
|
||||||
|
"precisionItem": {
|
||||||
|
"description": "PrecisionItem is type definition for testing strategic merge.",
|
||||||
|
"required": [],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "Name field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"int32": {
|
||||||
|
"description": "Int32 field.",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"int64": {
|
||||||
|
"description": "Int64 field.",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"float32": {
|
||||||
|
"description": "Float32 field.",
|
||||||
|
"type": "number",
|
||||||
|
"format": "float32"
|
||||||
|
},
|
||||||
|
"float64": {
|
||||||
|
"description": "Float64 field.",
|
||||||
|
"type": "number",
|
||||||
|
"format": "float64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-kubernetes-group-version-kind": [
|
||||||
|
{
|
||||||
|
"group": "fake-group",
|
||||||
|
"kind": "precisionItem",
|
||||||
|
"version": "some-version"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["openapi.go"],
|
||||||
|
importpath = "k8s.io/apimachinery/pkg/util/strategicpatch/testing",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
||||||
|
"//vendor/github.com/googleapis/gnostic/compiler:go_default_library",
|
||||||
|
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
||||||
|
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
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 testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/googleapis/gnostic/OpenAPIv2"
|
||||||
|
"github.com/googleapis/gnostic/compiler"
|
||||||
|
openapi "k8s.io/kube-openapi/pkg/util/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fake opens and returns a openapi swagger from a file Path. It will
|
||||||
|
// parse only once and then return the same copy everytime.
|
||||||
|
type Fake struct {
|
||||||
|
Path string
|
||||||
|
|
||||||
|
once sync.Once
|
||||||
|
document *openapi_v2.Document
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenAPISchema returns the openapi document and a potential error.
|
||||||
|
func (f *Fake) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||||
|
f.once.Do(func() {
|
||||||
|
_, err := os.Stat(f.Path)
|
||||||
|
if err != nil {
|
||||||
|
f.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
spec, err := ioutil.ReadFile(f.Path)
|
||||||
|
if err != nil {
|
||||||
|
f.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var info yaml.MapSlice
|
||||||
|
err = yaml.Unmarshal(spec, &info)
|
||||||
|
if err != nil {
|
||||||
|
f.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.document, f.err = openapi_v2.NewDocument(info, compiler.NewContext("$root", nil))
|
||||||
|
})
|
||||||
|
return f.document, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSchema(f Fake, model string) (openapi.Schema, error) {
|
||||||
|
s, err := f.OpenAPISchema()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m, err := openapi.NewOpenAPIData(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m.LookupModel(model), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSchemaOrDie returns returns the openapi schema.
|
||||||
|
func GetSchemaOrDie(f Fake, model string) openapi.Schema {
|
||||||
|
s, err := getSchema(f, model)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
193
staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/types.go
Normal file
193
staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/types.go
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
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 strategicpatch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/mergepatch"
|
||||||
|
openapi "k8s.io/kube-openapi/pkg/util/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
patchStrategyOpenapiextensionKey = "x-kubernetes-patch-strategy"
|
||||||
|
patchMergeKeyOpenapiextensionKey = "x-kubernetes-patch-merge-key"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LookupPatchItem interface {
|
||||||
|
openapi.SchemaVisitor
|
||||||
|
|
||||||
|
Error() error
|
||||||
|
Path() *openapi.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
type kindItem struct {
|
||||||
|
key string
|
||||||
|
path *openapi.Path
|
||||||
|
err error
|
||||||
|
patchmeta PatchMeta
|
||||||
|
subschema openapi.Schema
|
||||||
|
hasVisitKind bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKindItem(key string, path *openapi.Path) *kindItem {
|
||||||
|
return &kindItem{
|
||||||
|
key: key,
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ LookupPatchItem = &kindItem{}
|
||||||
|
|
||||||
|
func (item *kindItem) Error() error {
|
||||||
|
return item.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *kindItem) Path() *openapi.Path {
|
||||||
|
return item.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *kindItem) VisitPrimitive(schema *openapi.Primitive) {
|
||||||
|
item.err = errors.New("expected kind, but got primitive")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *kindItem) VisitArray(schema *openapi.Array) {
|
||||||
|
item.err = errors.New("expected kind, but got slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *kindItem) VisitMap(schema *openapi.Map) {
|
||||||
|
item.err = errors.New("expected kind, but got map")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *kindItem) VisitReference(schema openapi.Reference) {
|
||||||
|
if !item.hasVisitKind {
|
||||||
|
schema.SubSchema().Accept(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *kindItem) VisitKind(schema *openapi.Kind) {
|
||||||
|
subschema, ok := schema.Fields[item.key]
|
||||||
|
if !ok {
|
||||||
|
item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions())
|
||||||
|
if err != nil {
|
||||||
|
item.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.patchmeta = PatchMeta{
|
||||||
|
patchStrategies: patchStrategies,
|
||||||
|
patchMergeKey: mergeKey,
|
||||||
|
}
|
||||||
|
item.subschema = subschema
|
||||||
|
}
|
||||||
|
|
||||||
|
type sliceItem struct {
|
||||||
|
key string
|
||||||
|
path *openapi.Path
|
||||||
|
err error
|
||||||
|
patchmeta PatchMeta
|
||||||
|
subschema openapi.Schema
|
||||||
|
hasVisitKind bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSliceItem(key string, path *openapi.Path) *sliceItem {
|
||||||
|
return &sliceItem{
|
||||||
|
key: key,
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ LookupPatchItem = &sliceItem{}
|
||||||
|
|
||||||
|
func (item *sliceItem) Error() error {
|
||||||
|
return item.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *sliceItem) Path() *openapi.Path {
|
||||||
|
return item.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *sliceItem) VisitPrimitive(schema *openapi.Primitive) {
|
||||||
|
item.err = errors.New("expected slice, but got primitive")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *sliceItem) VisitArray(schema *openapi.Array) {
|
||||||
|
if !item.hasVisitKind {
|
||||||
|
item.err = errors.New("expected visit kind first, then visit array")
|
||||||
|
}
|
||||||
|
subschema := schema.SubType
|
||||||
|
item.subschema = subschema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *sliceItem) VisitMap(schema *openapi.Map) {
|
||||||
|
item.err = errors.New("expected slice, but got map")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *sliceItem) VisitReference(schema openapi.Reference) {
|
||||||
|
if !item.hasVisitKind {
|
||||||
|
schema.SubSchema().Accept(item)
|
||||||
|
} else {
|
||||||
|
item.subschema = schema.SubSchema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *sliceItem) VisitKind(schema *openapi.Kind) {
|
||||||
|
subschema, ok := schema.Fields[item.key]
|
||||||
|
if !ok {
|
||||||
|
item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions())
|
||||||
|
if err != nil {
|
||||||
|
item.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.patchmeta = PatchMeta{
|
||||||
|
patchStrategies: patchStrategies,
|
||||||
|
patchMergeKey: mergeKey,
|
||||||
|
}
|
||||||
|
item.hasVisitKind = true
|
||||||
|
subschema.Accept(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePatchMetadata(extensions map[string]interface{}) (string, []string, error) {
|
||||||
|
ps, foundPS := extensions[patchStrategyOpenapiextensionKey]
|
||||||
|
var patchStrategies []string
|
||||||
|
var mergeKey, patchStrategy string
|
||||||
|
var ok bool
|
||||||
|
if foundPS {
|
||||||
|
patchStrategy, ok = ps.(string)
|
||||||
|
if ok {
|
||||||
|
patchStrategies = strings.Split(patchStrategy, ",")
|
||||||
|
} else {
|
||||||
|
return "", nil, mergepatch.ErrBadArgType(patchStrategy, ps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mk, foundMK := extensions[patchMergeKeyOpenapiextensionKey]
|
||||||
|
if foundMK {
|
||||||
|
mergeKey, ok = mk.(string)
|
||||||
|
if !ok {
|
||||||
|
return "", nil, mergepatch.ErrBadArgType(mergeKey, mk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mergeKey, patchStrategies, nil
|
||||||
|
}
|
@ -26,17 +26,14 @@ const (
|
|||||||
// struct field given the struct type and the JSON name of the field.
|
// struct field given the struct type and the JSON name of the field.
|
||||||
// It returns field type, a slice of patch strategies, merge key and error.
|
// It returns field type, a slice of patch strategies, merge key and error.
|
||||||
// TODO: fix the returned errors to be introspectable.
|
// TODO: fix the returned errors to be introspectable.
|
||||||
func LookupPatchMetadata(t reflect.Type, jsonField string) (
|
func LookupPatchMetadataForStruct(t reflect.Type, jsonField string) (
|
||||||
elemType reflect.Type, patchStrategies []string, patchMergeKey string, e error) {
|
elemType reflect.Type, patchStrategies []string, patchMergeKey string, e error) {
|
||||||
if t.Kind() == reflect.Ptr {
|
if t.Kind() == reflect.Ptr {
|
||||||
t = t.Elem()
|
t = t.Elem()
|
||||||
}
|
}
|
||||||
if t.Kind() == reflect.Map {
|
|
||||||
elemType = t.Elem()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if t.Kind() != reflect.Struct {
|
if t.Kind() != reflect.Struct {
|
||||||
e = fmt.Errorf("merging an object in json but data type is not map or struct, instead is: %s",
|
e = fmt.Errorf("merging an object in json but data type is not struct, instead is: %s",
|
||||||
t.Kind().String())
|
t.Kind().String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ func TestLookupPtrToStruct(t *testing.T) {
|
|||||||
Inner []Elem `json:"inner" patchStrategy:"merge" patchMergeKey:"key"`
|
Inner []Elem `json:"inner" patchStrategy:"merge" patchMergeKey:"key"`
|
||||||
}
|
}
|
||||||
outer := &Outer{}
|
outer := &Outer{}
|
||||||
elemType, patchStrategies, patchMergeKey, err := LookupPatchMetadata(reflect.TypeOf(outer), "inner")
|
elemType, patchStrategies, patchMergeKey, err := LookupPatchMetadataForStruct(reflect.TypeOf(outer), "inner")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user