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