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  }