k8s.io/client-go@v0.31.1/util/csaupgrade/upgrade.go (about) 1 /* 2 Copyright 2022 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 csaupgrade 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "reflect" 25 26 "k8s.io/apimachinery/pkg/api/meta" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/util/sets" 30 "sigs.k8s.io/structured-merge-diff/v4/fieldpath" 31 ) 32 33 // Finds all managed fields owners of the given operation type which owns all of 34 // the fields in the given set 35 // 36 // If there is an error decoding one of the fieldsets for any reason, it is ignored 37 // and assumed not to match the query. 38 func FindFieldsOwners( 39 managedFields []metav1.ManagedFieldsEntry, 40 operation metav1.ManagedFieldsOperationType, 41 fields *fieldpath.Set, 42 ) []metav1.ManagedFieldsEntry { 43 var result []metav1.ManagedFieldsEntry 44 for _, entry := range managedFields { 45 if entry.Operation != operation { 46 continue 47 } 48 49 fieldSet, err := decodeManagedFieldsEntrySet(entry) 50 if err != nil { 51 continue 52 } 53 54 if fields.Difference(&fieldSet).Empty() { 55 result = append(result, entry) 56 } 57 } 58 return result 59 } 60 61 // Upgrades the Manager information for fields managed with client-side-apply (CSA) 62 // Prepares fields owned by `csaManager` for 'Update' operations for use now 63 // with the given `ssaManager` for `Apply` operations. 64 // 65 // This transformation should be performed on an object if it has been previously 66 // managed using client-side-apply to prepare it for future use with 67 // server-side-apply. 68 // 69 // Caveats: 70 // 1. This operation is not reversible. Information about which fields the client 71 // owned will be lost in this operation. 72 // 2. Supports being performed either before or after initial server-side apply. 73 // 3. Client-side apply tends to own more fields (including fields that are defaulted), 74 // this will possibly remove this defaults, they will be re-defaulted, that's fine. 75 // 4. Care must be taken to not overwrite the managed fields on the server if they 76 // have changed before sending a patch. 77 // 78 // obj - Target of the operation which has been managed with CSA in the past 79 // csaManagerNames - Names of FieldManagers to merge into ssaManagerName 80 // ssaManagerName - Name of FieldManager to be used for `Apply` operations 81 func UpgradeManagedFields( 82 obj runtime.Object, 83 csaManagerNames sets.Set[string], 84 ssaManagerName string, 85 opts ...Option, 86 ) error { 87 o := options{} 88 for _, opt := range opts { 89 opt(&o) 90 } 91 92 accessor, err := meta.Accessor(obj) 93 if err != nil { 94 return err 95 } 96 97 filteredManagers := accessor.GetManagedFields() 98 99 for csaManagerName := range csaManagerNames { 100 filteredManagers, err = upgradedManagedFields( 101 filteredManagers, csaManagerName, ssaManagerName, o) 102 103 if err != nil { 104 return err 105 } 106 } 107 108 // Commit changes to object 109 accessor.SetManagedFields(filteredManagers) 110 return nil 111 } 112 113 // Calculates a minimal JSON Patch to send to upgrade managed fields 114 // See `UpgradeManagedFields` for more information. 115 // 116 // obj - Target of the operation which has been managed with CSA in the past 117 // csaManagerNames - Names of FieldManagers to merge into ssaManagerName 118 // ssaManagerName - Name of FieldManager to be used for `Apply` operations 119 // 120 // Returns non-nil error if there was an error, a JSON patch, or nil bytes if 121 // there is no work to be done. 122 func UpgradeManagedFieldsPatch( 123 obj runtime.Object, 124 csaManagerNames sets.Set[string], 125 ssaManagerName string, 126 opts ...Option, 127 ) ([]byte, error) { 128 o := options{} 129 for _, opt := range opts { 130 opt(&o) 131 } 132 133 accessor, err := meta.Accessor(obj) 134 if err != nil { 135 return nil, err 136 } 137 138 managedFields := accessor.GetManagedFields() 139 filteredManagers := accessor.GetManagedFields() 140 for csaManagerName := range csaManagerNames { 141 filteredManagers, err = upgradedManagedFields( 142 filteredManagers, csaManagerName, ssaManagerName, o) 143 if err != nil { 144 return nil, err 145 } 146 } 147 148 if reflect.DeepEqual(managedFields, filteredManagers) { 149 // If the managed fields have not changed from the transformed version, 150 // there is no patch to perform 151 return nil, nil 152 } 153 154 // Create a patch with a diff between old and new objects. 155 // Just include all managed fields since that is only thing that will change 156 // 157 // Also include test for RV to avoid race condition 158 jsonPatch := []map[string]interface{}{ 159 { 160 "op": "replace", 161 "path": "/metadata/managedFields", 162 "value": filteredManagers, 163 }, 164 { 165 // Use "replace" instead of "test" operation so that etcd rejects with 166 // 409 conflict instead of apiserver with an invalid request 167 "op": "replace", 168 "path": "/metadata/resourceVersion", 169 "value": accessor.GetResourceVersion(), 170 }, 171 } 172 173 return json.Marshal(jsonPatch) 174 } 175 176 // Returns a copy of the provided managed fields that has been migrated from 177 // client-side-apply to server-side-apply, or an error if there was an issue 178 func upgradedManagedFields( 179 managedFields []metav1.ManagedFieldsEntry, 180 csaManagerName string, 181 ssaManagerName string, 182 opts options, 183 ) ([]metav1.ManagedFieldsEntry, error) { 184 if managedFields == nil { 185 return nil, nil 186 } 187 188 // Create managed fields clone since we modify the values 189 managedFieldsCopy := make([]metav1.ManagedFieldsEntry, len(managedFields)) 190 if copy(managedFieldsCopy, managedFields) != len(managedFields) { 191 return nil, errors.New("failed to copy managed fields") 192 } 193 managedFields = managedFieldsCopy 194 195 // Locate SSA manager 196 replaceIndex, managerExists := findFirstIndex(managedFields, 197 func(entry metav1.ManagedFieldsEntry) bool { 198 return entry.Manager == ssaManagerName && 199 entry.Operation == metav1.ManagedFieldsOperationApply && 200 entry.Subresource == opts.subresource 201 }) 202 203 if !managerExists { 204 // SSA manager does not exist. Find the most recent matching CSA manager, 205 // convert it to an SSA manager. 206 // 207 // (find first index, since managed fields are sorted so that most recent is 208 // first in the list) 209 replaceIndex, managerExists = findFirstIndex(managedFields, 210 func(entry metav1.ManagedFieldsEntry) bool { 211 return entry.Manager == csaManagerName && 212 entry.Operation == metav1.ManagedFieldsOperationUpdate && 213 entry.Subresource == opts.subresource 214 }) 215 216 if !managerExists { 217 // There are no CSA managers that need to be converted. Nothing to do 218 // Return early 219 return managedFields, nil 220 } 221 222 // Convert CSA manager into SSA manager 223 managedFields[replaceIndex].Operation = metav1.ManagedFieldsOperationApply 224 managedFields[replaceIndex].Manager = ssaManagerName 225 } 226 err := unionManagerIntoIndex(managedFields, replaceIndex, csaManagerName, opts) 227 if err != nil { 228 return nil, err 229 } 230 231 // Create version of managed fields which has no CSA managers with the given name 232 filteredManagers := filter(managedFields, func(entry metav1.ManagedFieldsEntry) bool { 233 return !(entry.Manager == csaManagerName && 234 entry.Operation == metav1.ManagedFieldsOperationUpdate && 235 entry.Subresource == opts.subresource) 236 }) 237 238 return filteredManagers, nil 239 } 240 241 // Locates an Update manager entry named `csaManagerName` with the same APIVersion 242 // as the manager at the targetIndex. Unions both manager's fields together 243 // into the manager specified by `targetIndex`. No other managers are modified. 244 func unionManagerIntoIndex( 245 entries []metav1.ManagedFieldsEntry, 246 targetIndex int, 247 csaManagerName string, 248 opts options, 249 ) error { 250 ssaManager := entries[targetIndex] 251 252 // find Update manager of same APIVersion, union ssa fields with it. 253 // discard all other Update managers of the same name 254 csaManagerIndex, csaManagerExists := findFirstIndex(entries, 255 func(entry metav1.ManagedFieldsEntry) bool { 256 return entry.Manager == csaManagerName && 257 entry.Operation == metav1.ManagedFieldsOperationUpdate && 258 entry.Subresource == opts.subresource && 259 entry.APIVersion == ssaManager.APIVersion 260 }) 261 262 targetFieldSet, err := decodeManagedFieldsEntrySet(ssaManager) 263 if err != nil { 264 return fmt.Errorf("failed to convert fields to set: %w", err) 265 } 266 267 combinedFieldSet := &targetFieldSet 268 269 // Union the csa manager with the existing SSA manager. Do nothing if 270 // there was no good candidate found 271 if csaManagerExists { 272 csaManager := entries[csaManagerIndex] 273 274 csaFieldSet, err := decodeManagedFieldsEntrySet(csaManager) 275 if err != nil { 276 return fmt.Errorf("failed to convert fields to set: %w", err) 277 } 278 279 combinedFieldSet = combinedFieldSet.Union(&csaFieldSet) 280 } 281 282 // Encode the fields back to the serialized format 283 err = encodeManagedFieldsEntrySet(&entries[targetIndex], *combinedFieldSet) 284 if err != nil { 285 return fmt.Errorf("failed to encode field set: %w", err) 286 } 287 288 return nil 289 } 290 291 func findFirstIndex[T any]( 292 collection []T, 293 predicate func(T) bool, 294 ) (int, bool) { 295 for idx, entry := range collection { 296 if predicate(entry) { 297 return idx, true 298 } 299 } 300 301 return -1, false 302 } 303 304 func filter[T any]( 305 collection []T, 306 predicate func(T) bool, 307 ) []T { 308 result := make([]T, 0, len(collection)) 309 310 for _, value := range collection { 311 if predicate(value) { 312 result = append(result, value) 313 } 314 } 315 316 if len(result) == 0 { 317 return nil 318 } 319 320 return result 321 } 322 323 // Included from fieldmanager.internal to avoid dependency cycle 324 // FieldsToSet creates a set paths from an input trie of fields 325 func decodeManagedFieldsEntrySet(f metav1.ManagedFieldsEntry) (s fieldpath.Set, err error) { 326 err = s.FromJSON(bytes.NewReader(f.FieldsV1.Raw)) 327 return s, err 328 } 329 330 // SetToFields creates a trie of fields from an input set of paths 331 func encodeManagedFieldsEntrySet(f *metav1.ManagedFieldsEntry, s fieldpath.Set) (err error) { 332 f.FieldsV1.Raw, err = s.ToJSON() 333 return err 334 }