github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/jsonmergepatch/patch.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 jsonmergepatch 18 19 import ( 20 "fmt" 21 "reflect" 22 23 "github.com/evanphx/json-patch" 24 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/json" 25 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/mergepatch" 26 ) 27 28 // Create a 3-way merge patch based-on JSON merge patch. 29 // Calculate addition-and-change patch between current and modified. 30 // Calculate deletion patch between original and modified. 31 func CreateThreeWayJSONMergePatch(original, modified, current []byte, fns ...mergepatch.PreconditionFunc) ([]byte, error) { 32 if len(original) == 0 { 33 original = []byte(`{}`) 34 } 35 if len(modified) == 0 { 36 modified = []byte(`{}`) 37 } 38 if len(current) == 0 { 39 current = []byte(`{}`) 40 } 41 42 addAndChangePatch, err := jsonpatch.CreateMergePatch(current, modified) 43 if err != nil { 44 return nil, err 45 } 46 // Only keep addition and changes 47 addAndChangePatch, addAndChangePatchObj, err := keepOrDeleteNullInJsonPatch(addAndChangePatch, false) 48 if err != nil { 49 return nil, err 50 } 51 52 deletePatch, err := jsonpatch.CreateMergePatch(original, modified) 53 if err != nil { 54 return nil, err 55 } 56 // Only keep deletion 57 deletePatch, deletePatchObj, err := keepOrDeleteNullInJsonPatch(deletePatch, true) 58 if err != nil { 59 return nil, err 60 } 61 62 hasConflicts, err := mergepatch.HasConflicts(addAndChangePatchObj, deletePatchObj) 63 if err != nil { 64 return nil, err 65 } 66 if hasConflicts { 67 return nil, mergepatch.NewErrConflict(mergepatch.ToYAMLOrError(addAndChangePatchObj), mergepatch.ToYAMLOrError(deletePatchObj)) 68 } 69 patch, err := jsonpatch.MergePatch(deletePatch, addAndChangePatch) 70 if err != nil { 71 return nil, err 72 } 73 74 var patchMap map[string]interface{} 75 err = json.Unmarshal(patch, &patchMap) 76 if err != nil { 77 return nil, fmt.Errorf("failed to unmarshal patch for precondition check: %s", patch) 78 } 79 meetPreconditions, err := meetPreconditions(patchMap, fns...) 80 if err != nil { 81 return nil, err 82 } 83 if !meetPreconditions { 84 return nil, mergepatch.NewErrPreconditionFailed(patchMap) 85 } 86 87 return patch, nil 88 } 89 90 // keepOrDeleteNullInJsonPatch takes a json-encoded byte array and a boolean. 91 // It returns a filtered object and its corresponding json-encoded byte array. 92 // It is a wrapper of func keepOrDeleteNullInObj 93 func keepOrDeleteNullInJsonPatch(patch []byte, keepNull bool) ([]byte, map[string]interface{}, error) { 94 var patchMap map[string]interface{} 95 err := json.Unmarshal(patch, &patchMap) 96 if err != nil { 97 return nil, nil, err 98 } 99 filteredMap, err := keepOrDeleteNullInObj(patchMap, keepNull) 100 if err != nil { 101 return nil, nil, err 102 } 103 o, err := json.Marshal(filteredMap) 104 return o, filteredMap, err 105 } 106 107 // keepOrDeleteNullInObj will keep only the null value and delete all the others, 108 // if keepNull is true. Otherwise, it will delete all the null value and keep the others. 109 func keepOrDeleteNullInObj(m map[string]interface{}, keepNull bool) (map[string]interface{}, error) { 110 filteredMap := make(map[string]interface{}) 111 var err error 112 for key, val := range m { 113 switch { 114 case keepNull && val == nil: 115 filteredMap[key] = nil 116 case val != nil: 117 switch typedVal := val.(type) { 118 case map[string]interface{}: 119 // Explicitly-set empty maps are treated as values instead of empty patches 120 if len(typedVal) == 0 { 121 if !keepNull { 122 filteredMap[key] = typedVal 123 } 124 continue 125 } 126 127 var filteredSubMap map[string]interface{} 128 filteredSubMap, err = keepOrDeleteNullInObj(typedVal, keepNull) 129 if err != nil { 130 return nil, err 131 } 132 133 // If the returned filtered submap was empty, this is an empty patch for the entire subdict, so the key 134 // should not be set 135 if len(filteredSubMap) != 0 { 136 filteredMap[key] = filteredSubMap 137 } 138 139 case []interface{}, string, float64, bool, int64, nil: 140 // Lists are always replaced in Json, no need to check each entry in the list. 141 if !keepNull { 142 filteredMap[key] = val 143 } 144 default: 145 return nil, fmt.Errorf("unknown type: %v", reflect.TypeOf(typedVal)) 146 } 147 } 148 } 149 return filteredMap, nil 150 } 151 152 func meetPreconditions(patchObj map[string]interface{}, fns ...mergepatch.PreconditionFunc) (bool, error) { 153 // Apply the preconditions to the patch, and return an error if any of them fail. 154 for _, fn := range fns { 155 if !fn(patchObj) { 156 return false, fmt.Errorf("precondition failed for: %v", patchObj) 157 } 158 } 159 return true, nil 160 }