sigs.k8s.io/cluster-api@v1.7.1/util/patch/utils.go (about) 1 /* 2 Copyright 2020 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 patch 18 19 import ( 20 "strings" 21 22 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 23 "k8s.io/apimachinery/pkg/runtime" 24 "k8s.io/apimachinery/pkg/runtime/schema" 25 ) 26 27 type patchType string 28 29 func (p patchType) Key() string { 30 return strings.Split(string(p), ".")[0] 31 } 32 33 const ( 34 specPatch patchType = "spec" 35 statusPatch patchType = "status" 36 ) 37 38 var ( 39 preserveUnstructuredKeys = map[string]bool{ 40 "kind": true, 41 "apiVersion": true, 42 "metadata": true, 43 } 44 ) 45 46 func unstructuredHasStatus(u *unstructured.Unstructured) bool { 47 _, ok := u.Object["status"] 48 return ok 49 } 50 51 // toUnstructured converts an object to Unstructured. 52 // We have to pass in a gvk as we can't rely on GVK being set in a runtime.Object. 53 func toUnstructured(obj runtime.Object, gvk schema.GroupVersionKind) (*unstructured.Unstructured, error) { 54 // If the incoming object is already unstructured, perform a deep copy first 55 // otherwise DefaultUnstructuredConverter ends up returning the inner map without 56 // making a copy. 57 if _, ok := obj.(runtime.Unstructured); ok { 58 obj = obj.DeepCopyObject() 59 } 60 rawMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) 61 if err != nil { 62 return nil, err 63 } 64 u := &unstructured.Unstructured{Object: rawMap} 65 u.SetGroupVersionKind(gvk) 66 67 return u, nil 68 } 69 70 // unsafeUnstructuredCopy returns a shallow copy of the unstructured object given as input. 71 // It copies the common fields such as `kind`, `apiVersion`, `metadata` and the patchType specified. 72 // 73 // It's not safe to modify any of the keys in the returned unstructured object, the result should be treated as read-only. 74 func unsafeUnstructuredCopy(obj *unstructured.Unstructured, focus patchType, isConditionsSetter bool) *unstructured.Unstructured { 75 // Create the return focused-unstructured object with a preallocated map. 76 res := &unstructured.Unstructured{Object: make(map[string]interface{}, len(obj.Object))} 77 78 // Ranges over the keys of the unstructured object, think of this as the very top level of an object 79 // when submitting a yaml to kubectl or a client. 80 // These would be keys like `apiVersion`, `kind`, `metadata`, `spec`, `status`, etc. 81 for key := range obj.Object { 82 value := obj.Object[key] 83 84 // Perform a shallow copy only for the keys we're interested in, or the ones that should be always preserved. 85 if key == focus.Key() || preserveUnstructuredKeys[key] { 86 res.Object[key] = value 87 } 88 89 // If we've determined that we're able to interface with conditions.Setter interface, 90 // when dealing with the status patch, remove the status.conditions sub-field from the object. 91 if isConditionsSetter && focus == statusPatch { 92 // NOTE: Removing status.conditions changes the incoming object! This is safe because the condition patch 93 // doesn't use the unstructured fields, and it runs before any other patch. 94 // 95 // If we want to be 100% safe, we could make a copy of the incoming object before modifying it, although 96 // copies have a high cpu and high memory usage, therefore we intentionally choose to avoid extra copies 97 // given that the ordering of operations and safety is handled internally by the patch helper. 98 unstructured.RemoveNestedField(res.Object, "status", "conditions") 99 } 100 } 101 102 return res 103 }