1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package mergepatch 18 19import ( 20 "fmt" 21 "reflect" 22 23 "github.com/davecgh/go-spew/spew" 24 "sigs.k8s.io/yaml" 25) 26 27// PreconditionFunc asserts that an incompatible change is not present within a patch. 28type PreconditionFunc func(interface{}) bool 29 30// RequireKeyUnchanged returns a precondition function that fails if the provided key 31// is present in the patch (indicating that its value has changed). 32func RequireKeyUnchanged(key string) PreconditionFunc { 33 return func(patch interface{}) bool { 34 patchMap, ok := patch.(map[string]interface{}) 35 if !ok { 36 return true 37 } 38 39 // The presence of key means that its value has been changed, so the test fails. 40 _, ok = patchMap[key] 41 return !ok 42 } 43} 44 45// RequireMetadataKeyUnchanged creates a precondition function that fails 46// if the metadata.key is present in the patch (indicating its value 47// has changed). 48func RequireMetadataKeyUnchanged(key string) PreconditionFunc { 49 return func(patch interface{}) bool { 50 patchMap, ok := patch.(map[string]interface{}) 51 if !ok { 52 return true 53 } 54 patchMap1, ok := patchMap["metadata"] 55 if !ok { 56 return true 57 } 58 patchMap2, ok := patchMap1.(map[string]interface{}) 59 if !ok { 60 return true 61 } 62 _, ok = patchMap2[key] 63 return !ok 64 } 65} 66 67func ToYAMLOrError(v interface{}) string { 68 y, err := toYAML(v) 69 if err != nil { 70 return err.Error() 71 } 72 73 return y 74} 75 76func toYAML(v interface{}) (string, error) { 77 y, err := yaml.Marshal(v) 78 if err != nil { 79 return "", fmt.Errorf("yaml marshal failed:%v\n%v\n", err, spew.Sdump(v)) 80 } 81 82 return string(y), nil 83} 84 85// HasConflicts returns true if the left and right JSON interface objects overlap with 86// different values in any key. All keys are required to be strings. Since patches of the 87// same Type have congruent keys, this is valid for multiple patch types. This method 88// supports JSON merge patch semantics. 89// 90// NOTE: Numbers with different types (e.g. int(0) vs int64(0)) will be detected as conflicts. 91// Make sure the unmarshaling of left and right are consistent (e.g. use the same library). 92func HasConflicts(left, right interface{}) (bool, error) { 93 switch typedLeft := left.(type) { 94 case map[string]interface{}: 95 switch typedRight := right.(type) { 96 case map[string]interface{}: 97 for key, leftValue := range typedLeft { 98 rightValue, ok := typedRight[key] 99 if !ok { 100 continue 101 } 102 if conflict, err := HasConflicts(leftValue, rightValue); err != nil || conflict { 103 return conflict, err 104 } 105 } 106 107 return false, nil 108 default: 109 return true, nil 110 } 111 case []interface{}: 112 switch typedRight := right.(type) { 113 case []interface{}: 114 if len(typedLeft) != len(typedRight) { 115 return true, nil 116 } 117 118 for i := range typedLeft { 119 if conflict, err := HasConflicts(typedLeft[i], typedRight[i]); err != nil || conflict { 120 return conflict, err 121 } 122 } 123 124 return false, nil 125 default: 126 return true, nil 127 } 128 case string, float64, bool, int64, nil: 129 return !reflect.DeepEqual(left, right), nil 130 default: 131 return true, fmt.Errorf("unknown type: %v", reflect.TypeOf(left)) 132 } 133} 134