k8s.io/apimachinery@v0.29.2/pkg/util/managedfields/scalehandler.go (about) 1 /* 2 Copyright 2021 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 managedfields 18 19 import ( 20 "fmt" 21 22 "k8s.io/apimachinery/pkg/api/meta" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 25 "k8s.io/apimachinery/pkg/runtime/schema" 26 "k8s.io/apimachinery/pkg/util/managedfields/internal" 27 "sigs.k8s.io/structured-merge-diff/v4/fieldpath" 28 ) 29 30 var ( 31 scaleGroupVersion = schema.GroupVersion{Group: "autoscaling", Version: "v1"} 32 replicasPathInScale = fieldpath.MakePathOrDie("spec", "replicas") 33 ) 34 35 // ResourcePathMappings maps a group/version to its replicas path. The 36 // assumption is that all the paths correspond to leaf fields. 37 type ResourcePathMappings map[string]fieldpath.Path 38 39 // ScaleHandler manages the conversion of managed fields between a main 40 // resource and the scale subresource 41 type ScaleHandler struct { 42 parentEntries []metav1.ManagedFieldsEntry 43 groupVersion schema.GroupVersion 44 mappings ResourcePathMappings 45 } 46 47 // NewScaleHandler creates a new ScaleHandler 48 func NewScaleHandler(parentEntries []metav1.ManagedFieldsEntry, groupVersion schema.GroupVersion, mappings ResourcePathMappings) *ScaleHandler { 49 return &ScaleHandler{ 50 parentEntries: parentEntries, 51 groupVersion: groupVersion, 52 mappings: mappings, 53 } 54 } 55 56 // ToSubresource filter the managed fields of the main resource and convert 57 // them so that they can be handled by scale. 58 // For the managed fields that have a replicas path it performs two changes: 59 // 1. APIVersion is changed to the APIVersion of the scale subresource 60 // 2. Replicas path of the main resource is transformed to the replicas path of 61 // the scale subresource 62 func (h *ScaleHandler) ToSubresource() ([]metav1.ManagedFieldsEntry, error) { 63 managed, err := internal.DecodeManagedFields(h.parentEntries) 64 if err != nil { 65 return nil, err 66 } 67 68 f := fieldpath.ManagedFields{} 69 t := map[string]*metav1.Time{} 70 for manager, versionedSet := range managed.Fields() { 71 path, ok := h.mappings[string(versionedSet.APIVersion())] 72 // Skip the entry if the APIVersion is unknown 73 if !ok || path == nil { 74 continue 75 } 76 77 if versionedSet.Set().Has(path) { 78 newVersionedSet := fieldpath.NewVersionedSet( 79 fieldpath.NewSet(replicasPathInScale), 80 fieldpath.APIVersion(scaleGroupVersion.String()), 81 versionedSet.Applied(), 82 ) 83 84 f[manager] = newVersionedSet 85 t[manager] = managed.Times()[manager] 86 } 87 } 88 89 return managedFieldsEntries(internal.NewManaged(f, t)) 90 } 91 92 // ToParent merges `scaleEntries` with the entries of the main resource and 93 // transforms them accordingly 94 func (h *ScaleHandler) ToParent(scaleEntries []metav1.ManagedFieldsEntry) ([]metav1.ManagedFieldsEntry, error) { 95 decodedParentEntries, err := internal.DecodeManagedFields(h.parentEntries) 96 if err != nil { 97 return nil, err 98 } 99 parentFields := decodedParentEntries.Fields() 100 101 decodedScaleEntries, err := internal.DecodeManagedFields(scaleEntries) 102 if err != nil { 103 return nil, err 104 } 105 scaleFields := decodedScaleEntries.Fields() 106 107 f := fieldpath.ManagedFields{} 108 t := map[string]*metav1.Time{} 109 110 for manager, versionedSet := range parentFields { 111 // Get the main resource "replicas" path 112 path, ok := h.mappings[string(versionedSet.APIVersion())] 113 // Drop the entry if the APIVersion is unknown. 114 if !ok { 115 continue 116 } 117 118 // If the parent entry does not have the replicas path or it is nil, just 119 // keep it as it is. The path is nil for Custom Resources without scale 120 // subresource. 121 if path == nil || !versionedSet.Set().Has(path) { 122 f[manager] = versionedSet 123 t[manager] = decodedParentEntries.Times()[manager] 124 continue 125 } 126 127 if _, ok := scaleFields[manager]; !ok { 128 // "Steal" the replicas path from the main resource entry 129 newSet := versionedSet.Set().Difference(fieldpath.NewSet(path)) 130 131 if !newSet.Empty() { 132 newVersionedSet := fieldpath.NewVersionedSet( 133 newSet, 134 versionedSet.APIVersion(), 135 versionedSet.Applied(), 136 ) 137 f[manager] = newVersionedSet 138 t[manager] = decodedParentEntries.Times()[manager] 139 } 140 } else { 141 // Field wasn't stolen, let's keep the entry as it is. 142 f[manager] = versionedSet 143 t[manager] = decodedParentEntries.Times()[manager] 144 delete(scaleFields, manager) 145 } 146 } 147 148 for manager, versionedSet := range scaleFields { 149 if !versionedSet.Set().Has(replicasPathInScale) { 150 continue 151 } 152 newVersionedSet := fieldpath.NewVersionedSet( 153 fieldpath.NewSet(h.mappings[h.groupVersion.String()]), 154 fieldpath.APIVersion(h.groupVersion.String()), 155 versionedSet.Applied(), 156 ) 157 f[manager] = newVersionedSet 158 t[manager] = decodedParentEntries.Times()[manager] 159 } 160 161 return managedFieldsEntries(internal.NewManaged(f, t)) 162 } 163 164 func managedFieldsEntries(entries internal.ManagedInterface) ([]metav1.ManagedFieldsEntry, error) { 165 obj := &unstructured.Unstructured{Object: map[string]interface{}{}} 166 if err := internal.EncodeObjectManagedFields(obj, entries); err != nil { 167 return nil, err 168 } 169 accessor, err := meta.Accessor(obj) 170 if err != nil { 171 panic(fmt.Sprintf("couldn't get accessor: %v", err)) 172 } 173 return accessor.GetManagedFields(), nil 174 }