github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/lib/operatorclient/patch.go (about) 1 package operatorclient 2 3 import ( 4 "encoding/json" 5 "fmt" 6 7 appsv1 "k8s.io/api/apps/v1" 8 v1 "k8s.io/api/core/v1" 9 extensionsv1beta1 "k8s.io/api/extensions/v1beta1" 10 v1beta1ext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" 11 "k8s.io/apimachinery/pkg/api/meta" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/runtime" 14 "k8s.io/apimachinery/pkg/util/strategicpatch" 15 ) 16 17 // UpdateFunction defines a function that updates an object in an Update* function. The function 18 // provides the current instance of the object retrieved from the apiserver. The function should 19 // return the updated object to be applied. 20 type UpdateFunction func(current metav1.Object) (metav1.Object, error) 21 22 // Update returns a default UpdateFunction implementation that passes its argument through to the 23 // Update* function directly, ignoring the current object. 24 // 25 // Example usage: 26 // 27 // client.UpdateDaemonSet(namespace, name, types.Update(obj)) 28 func Update(obj metav1.Object) UpdateFunction { 29 return func(_ metav1.Object) (metav1.Object, error) { 30 return obj, nil 31 } 32 } 33 34 // PatchFunction defines a function that is used to provide patch objects for a 3-way merge. The 35 // function provides the current instance of the object retrieved from the apiserver. The function 36 // should return the "original" and "modified" objects (in that order) for 3-way patch computation. 37 type PatchFunction func(current metav1.Object) (metav1.Object, metav1.Object, error) 38 39 // Patch returns a default PatchFunction implementation that passes its arguments through to the 40 // patcher directly, ignoring the current object. 41 // 42 // Example usage: 43 // 44 // client.PatchDaemonSet(namespace, name, types.Patch(original, current)) 45 func Patch(original metav1.Object, modified metav1.Object) PatchFunction { 46 return func(_ metav1.Object) (metav1.Object, metav1.Object, error) { 47 return original, modified, nil 48 } 49 } 50 51 // updateToPatch wraps an UpdateFunction as a PatchFunction. 52 func updateToPatch(f UpdateFunction) PatchFunction { 53 return func(obj metav1.Object) (metav1.Object, metav1.Object, error) { 54 obj, err := f(obj) 55 return nil, obj, err 56 } 57 } 58 59 func createPatch(original, modified runtime.Object) ([]byte, error) { 60 originalData, err := json.Marshal(original) 61 if err != nil { 62 return nil, err 63 } 64 modifiedData, err := json.Marshal(modified) 65 if err != nil { 66 return nil, err 67 } 68 return strategicpatch.CreateTwoWayMergePatch(originalData, modifiedData, original) 69 } 70 71 func createThreeWayMergePatchPreservingCommands(original, modified, current runtime.Object) ([]byte, error) { 72 var datastruct runtime.Object 73 switch { 74 case original != nil: 75 datastruct = original 76 case modified != nil: 77 datastruct = modified 78 case current != nil: 79 datastruct = current 80 default: 81 return nil, nil // A 3-way merge of `nil`s is `nil`. 82 } 83 patchMeta, err := strategicpatch.NewPatchMetaFromStruct(datastruct) 84 if err != nil { 85 return nil, err 86 } 87 88 // Create normalized clones of objects. 89 original, err = cloneAndNormalizeObject(original) 90 if err != nil { 91 return nil, err 92 } 93 modified, err = cloneAndNormalizeObject(modified) 94 if err != nil { 95 return nil, err 96 } 97 current, err = cloneAndNormalizeObject(current) 98 if err != nil { 99 return nil, err 100 } 101 // Perform 3-way merge of annotations and labels. 102 if err := mergeAnnotationsAndLabels(original, modified, current); err != nil { 103 return nil, err 104 } 105 // Construct 3-way JSON merge patch. 106 originalData, err := json.Marshal(original) 107 if err != nil { 108 return nil, err 109 } 110 modifiedData, err := json.Marshal(modified) 111 if err != nil { 112 return nil, err 113 } 114 currentData, err := json.Marshal(current) 115 if err != nil { 116 return nil, err 117 } 118 return strategicpatch.CreateThreeWayMergePatch(originalData, modifiedData, currentData, patchMeta, false /* overwrite */) 119 } 120 121 func cloneAndNormalizeObject(obj runtime.Object) (runtime.Object, error) { 122 if obj == nil { 123 return obj, nil 124 } 125 126 // Clone the object since it will be modified. 127 obj = obj.DeepCopyObject() 128 switch obj := obj.(type) { 129 case *appsv1.DaemonSet: 130 if obj != nil { 131 // These are only extracted from current; should not be considered for diffs. 132 obj.ObjectMeta.ResourceVersion = "" 133 obj.ObjectMeta.CreationTimestamp = metav1.Time{} 134 obj.Status = appsv1.DaemonSetStatus{} 135 } 136 case *appsv1.Deployment: 137 if obj != nil { 138 // These are only extracted from current; should not be considered for diffs. 139 obj.ObjectMeta.ResourceVersion = "" 140 obj.ObjectMeta.CreationTimestamp = metav1.Time{} 141 obj.Status = appsv1.DeploymentStatus{} 142 } 143 case *v1.Service: 144 if obj != nil { 145 // These are only extracted from current; should not be considered for diffs. 146 obj.ObjectMeta.ResourceVersion = "" 147 obj.ObjectMeta.CreationTimestamp = metav1.Time{} 148 obj.Status = v1.ServiceStatus{} 149 // ClusterIP for service is immutable, so cannot patch. 150 obj.Spec.ClusterIP = "" 151 } 152 case *extensionsv1beta1.Ingress: 153 if obj != nil { 154 // These are only extracted from current; should not be considered for diffs. 155 obj.ObjectMeta.ResourceVersion = "" 156 obj.ObjectMeta.CreationTimestamp = metav1.Time{} 157 obj.Status = extensionsv1beta1.IngressStatus{} 158 } 159 case *v1beta1ext.CustomResourceDefinition: 160 if obj != nil { 161 // These are only extracted from current; should not be considered for diffs. 162 obj.ObjectMeta.ResourceVersion = "" 163 obj.ObjectMeta.CreationTimestamp = metav1.Time{} 164 obj.ObjectMeta.SelfLink = "" 165 obj.ObjectMeta.UID = "" 166 obj.Status = v1beta1ext.CustomResourceDefinitionStatus{} 167 } 168 default: 169 return nil, fmt.Errorf("unhandled type: %T", obj) 170 } 171 return obj, nil 172 } 173 174 // mergeAnnotationsAndLabels performs a 3-way merge of all annotations and labels using custom 175 // 3-way merge logic defined in mergeMaps() below. 176 func mergeAnnotationsAndLabels(original, modified, current runtime.Object) error { 177 if original == nil || modified == nil || current == nil { 178 return nil 179 } 180 181 accessor := meta.NewAccessor() 182 if err := mergeMaps(original, modified, current, accessor.Annotations, accessor.SetAnnotations); err != nil { 183 return err 184 } 185 if err := mergeMaps(original, modified, current, accessor.Labels, accessor.SetLabels); err != nil { 186 return err 187 } 188 189 switch current := current.(type) { 190 case *appsv1.DaemonSet: 191 getter := func(obj runtime.Object) (map[string]string, error) { 192 return obj.(*appsv1.DaemonSet).Spec.Template.Annotations, nil 193 } 194 setter := func(obj runtime.Object, val map[string]string) error { 195 obj.(*appsv1.DaemonSet).Spec.Template.Annotations = val 196 return nil 197 } 198 if err := mergeMaps(original, modified, current, getter, setter); err != nil { 199 return err 200 } 201 getter = func(obj runtime.Object) (map[string]string, error) { 202 return obj.(*appsv1.DaemonSet).Spec.Template.Labels, nil 203 } 204 setter = func(obj runtime.Object, val map[string]string) error { 205 obj.(*appsv1.DaemonSet).Spec.Template.Labels = val 206 return nil 207 } 208 if err := mergeMaps(original, modified, current, getter, setter); err != nil { 209 return err 210 } 211 case *appsv1.Deployment: 212 getter := func(obj runtime.Object) (map[string]string, error) { 213 return obj.(*appsv1.Deployment).Spec.Template.Annotations, nil 214 } 215 setter := func(obj runtime.Object, val map[string]string) error { 216 obj.(*appsv1.Deployment).Spec.Template.Annotations = val 217 return nil 218 } 219 if err := mergeMaps(original, modified, current, getter, setter); err != nil { 220 return err 221 } 222 getter = func(obj runtime.Object) (map[string]string, error) { 223 return obj.(*appsv1.Deployment).Spec.Template.Labels, nil 224 } 225 setter = func(obj runtime.Object, val map[string]string) error { 226 obj.(*appsv1.Deployment).Spec.Template.Labels = val 227 return nil 228 } 229 if err := mergeMaps(original, modified, current, getter, setter); err != nil { 230 return err 231 } 232 } 233 return nil 234 } 235 236 // mergeMaps creates a patch using createThreeWayMapPatch and if the patch is non-empty applies 237 // the patch to the input. The getter and setter are used to access the map inside the given 238 // objects. 239 func mergeMaps(original, modified, current runtime.Object, getter func(runtime.Object) (map[string]string, error), setter func(runtime.Object, map[string]string) error) error { 240 originalMap, err := getter(original) 241 if err != nil { 242 return err 243 } 244 modifiedMap, err := getter(modified) 245 if err != nil { 246 return err 247 } 248 currentMap, err := getter(current) 249 if err != nil { 250 return err 251 } 252 253 patch, err := createThreeWayMapPatch(originalMap, modifiedMap, currentMap) 254 if err != nil { 255 return err 256 } 257 if len(patch) == 0 { 258 return nil // nothing to apply. 259 } 260 modifiedMap = applyMapPatch(originalMap, currentMap, patch) 261 262 if err := setter(original, originalMap); err != nil { 263 return err 264 } 265 if err := setter(modified, modifiedMap); err != nil { 266 return err 267 } 268 return setter(current, currentMap) 269 } 270 271 // applyMapPatch creates a copy of current and applies the three-way map patch to it. 272 func applyMapPatch(original, current map[string]string, patch map[string]interface{}) map[string]string { 273 merged := make(map[string]string, len(current)) 274 for k, v := range current { 275 merged[k] = v 276 } 277 for k, v := range patch { 278 if v == nil { 279 delete(merged, k) 280 } else { 281 merged[k] = v.(string) 282 if _, ok := current[k]; !ok { 283 // If we are re-adding something that may have already been in original then ensure it is 284 // removed from `original` to avoid a conflict in upstream patch code. 285 delete(original, k) 286 } 287 } 288 } 289 return merged 290 } 291 292 // createThreeWayMapPatch constructs a 3-way patch between original, modified, and current. The 293 // patch contains only keys that are added, keys that are removed (with their values set to nil) or 294 // keys whose values are modified. Returns an error if there is a conflict for any key. 295 // 296 // The behavior is defined as follows: 297 // 298 // - If an item is present in modified, ensure it exists in current. 299 // - If an item is present in original and removed in modified, remove it from current. 300 // - If an item is present only in current, leave it as-is. 301 // 302 // This effectively "enforces" that all items present in modified are present in current, and all 303 // items deleted from original => modified are deleted in current. 304 // 305 // The following will cause a conflict: 306 // 307 // (1) An item was deleted from original => modified but modified from original => current. 308 // (2) An item was modified differently from original => modified and original => current. 309 func createThreeWayMapPatch(original, modified, current map[string]string) (map[string]interface{}, error) { 310 // Create union of keys. 311 keys := make(map[string]struct{}) 312 for k := range original { 313 keys[k] = struct{}{} 314 } 315 for k := range modified { 316 keys[k] = struct{}{} 317 } 318 for k := range current { 319 keys[k] = struct{}{} 320 } 321 322 // Create patch according to rules. 323 patch := make(map[string]interface{}) 324 for k := range keys { 325 oVal, oOk := original[k] 326 mVal, mOk := modified[k] 327 cVal, cOk := current[k] 328 329 switch { 330 case oOk && mOk && cOk: 331 // present in all three. 332 if mVal != cVal { 333 if oVal != cVal { 334 // conflict type 2: changed to different values in modified and current. 335 return nil, fmt.Errorf("conflict at key %v: original = %v, modified = %v, current = %v", k, oVal, mVal, cVal) 336 } 337 patch[k] = mVal 338 } 339 case !oOk && mOk && cOk: 340 // added in modified and current. 341 if mVal != cVal { 342 // conflict type 2: added different values in modified and current. 343 return nil, fmt.Errorf("conflict at key %v: original = <absent>, modified = %v, current = %v", k, mVal, cVal) 344 } 345 case oOk && !mOk && cOk: 346 // deleted in modified. 347 if oVal != cVal { 348 // conflict type 1: changed from original to current, removed in modified. 349 return nil, fmt.Errorf("conflict at key %v, original = %v, modified = <absent>, current = %v", k, oVal, cVal) 350 } 351 patch[k] = nil 352 case oOk && mOk && !cOk: 353 // deleted in current. 354 patch[k] = mVal 355 case !oOk && !mOk && cOk: 356 // only exists in current. 357 case !oOk && mOk && !cOk: 358 // added in modified. 359 patch[k] = mVal 360 case oOk && !mOk && !cOk: 361 // deleted in both modified and current. 362 case !oOk && !mOk && !cOk: 363 // unreachable. 364 } 365 } 366 return patch, nil 367 }