github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/mergepatch/util.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package mergepatch 18 19 import ( 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. 28 type 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). 32 func 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). 48 func 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 67 func ToYAMLOrError(v interface{}) string { 68 y, err := toYAML(v) 69 if err != nil { 70 return err.Error() 71 } 72 73 return y 74 } 75 76 func 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 // 92 // Make sure the unmarshaling of left and right are consistent (e.g. use the same library). 93 func HasConflicts(left, right interface{}) (bool, error) { 94 switch typedLeft := left.(type) { 95 case map[string]interface{}: 96 switch typedRight := right.(type) { 97 case map[string]interface{}: 98 for key, leftValue := range typedLeft { 99 rightValue, ok := typedRight[key] 100 if !ok { 101 continue 102 } 103 if conflict, err := HasConflicts(leftValue, rightValue); err != nil || conflict { 104 return conflict, err 105 } 106 } 107 108 return false, nil 109 default: 110 return true, nil 111 } 112 case []interface{}: 113 switch typedRight := right.(type) { 114 case []interface{}: 115 if len(typedLeft) != len(typedRight) { 116 return true, nil 117 } 118 119 for i := range typedLeft { 120 if conflict, err := HasConflicts(typedLeft[i], typedRight[i]); err != nil || conflict { 121 return conflict, err 122 } 123 } 124 125 return false, nil 126 default: 127 return true, nil 128 } 129 case string, float64, bool, int64, nil: 130 return !reflect.DeepEqual(left, right), nil 131 default: 132 return true, fmt.Errorf("unknown type: %v", reflect.TypeOf(left)) 133 } 134 }