sigs.k8s.io/cluster-api@v1.7.1/internal/util/ssa/managedfields.go (about) 1 /* 2 Copyright 2023 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 ssa contains utils related to Server-Side-Apply. 18 package ssa 19 20 import ( 21 "context" 22 "encoding/json" 23 24 "github.com/pkg/errors" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/klog/v2" 27 "sigs.k8s.io/controller-runtime/pkg/client" 28 "sigs.k8s.io/controller-runtime/pkg/client/apiutil" 29 30 "sigs.k8s.io/cluster-api/internal/contract" 31 ) 32 33 const classicManager = "manager" 34 35 // DropManagedFields modifies the managedFields entries on the object that belong to "manager" (Operation=Update) 36 // to drop ownership of the given paths if there is no field yet that is managed by `ssaManager`. 37 // 38 // If we want to be able to drop fields that were previously owned by the "manager" we have to ensure that 39 // fields are not co-owned by "manager" and `ssaManager`. Otherwise, when we drop the fields with SSA 40 // (i.e. `ssaManager`) the fields would remain as they are still owned by "manager". 41 // The following code will do a one-time update on the managed fields. 42 // We won't do this on subsequent reconciles. This case will be identified by checking if `ssaManager` owns any fields. 43 // Dropping ownership in paths for existing "manager" entries (which could also be from other controllers) is safe, 44 // as we assume that if other controllers are still writing fields on the object they will just do it again and thus 45 // gain ownership again. 46 func DropManagedFields(ctx context.Context, c client.Client, obj client.Object, ssaManager string, paths []contract.Path) error { 47 // Return if `ssaManager` already owns any fields. 48 if hasFieldsManagedBy(obj, ssaManager) { 49 return nil 50 } 51 52 // Since there is no field managed by `ssaManager` it means that 53 // this is the first time this object is being processed after the controller calling this function 54 // started to use SSA patches. 55 // It is required to clean-up managedFields from entries created by the regular patches. 56 // This will ensure that `ssaManager` will be able to modify the fields that 57 // were originally owned by "manager". 58 base := obj.DeepCopyObject().(client.Object) 59 60 // Modify managedFieldEntry for manager=manager and operation=update to drop ownership 61 // for the given paths to avoid having two managers holding values. 62 originalManagedFields := obj.GetManagedFields() 63 managedFields := make([]metav1.ManagedFieldsEntry, 0, len(originalManagedFields)) 64 for _, managedField := range originalManagedFields { 65 if managedField.Manager == classicManager && 66 managedField.Operation == metav1.ManagedFieldsOperationUpdate { 67 // Unmarshal the managed fields into a map[string]interface{} 68 fieldsV1 := map[string]interface{}{} 69 if err := json.Unmarshal(managedField.FieldsV1.Raw, &fieldsV1); err != nil { 70 return errors.Wrap(err, "failed to unmarshal managed fields") 71 } 72 73 // Filter out the ownership for the given paths. 74 FilterIntent(&FilterIntentInput{ 75 Path: contract.Path{}, 76 Value: fieldsV1, 77 ShouldFilter: IsPathIgnored(paths), 78 }) 79 80 fieldsV1Raw, err := json.Marshal(fieldsV1) 81 if err != nil { 82 return errors.Wrap(err, "failed to marshal managed fields") 83 } 84 managedField.FieldsV1.Raw = fieldsV1Raw 85 86 managedFields = append(managedFields, managedField) 87 } else { 88 // Do not modify the entry. Use as is. 89 managedFields = append(managedFields, managedField) 90 } 91 } 92 93 obj.SetManagedFields(managedFields) 94 95 return c.Patch(ctx, obj, client.MergeFrom(base)) 96 } 97 98 // CleanUpManagedFieldsForSSAAdoption deletes the managedFields entries on the object that belong to "manager" (Operation=Update) 99 // if there is no field yet that is managed by `ssaManager`. 100 // It adds an "empty" entry in managedFields of the object if no field is currently managed by `ssaManager`. 101 // 102 // In previous versions of Cluster API (< v1.4.0) we were writing objects with Create and Patch which resulted in fields 103 // being owned by the "manager". After switching to Server-Side-Apply (SSA), fields will be owned by `ssaManager`. 104 // 105 // If we want to be able to drop fields that were previously owned by the "manager" we have to ensure that 106 // fields are not co-owned by "manager" and `ssaManager`. Otherwise, when we drop the fields with SSA 107 // (i.e. `ssaManager`) the fields would remain as they are still owned by "manager". 108 // The following code will do a one-time update on the managed fields to drop all entries for "manager". 109 // We won't do this on subsequent reconciles. This case will be identified by checking if `ssaManager` owns any fields. 110 // Dropping all existing "manager" entries (which could also be from other controllers) is safe, as we assume that if 111 // other controllers are still writing fields on the object they will just do it again and thus gain ownership again. 112 func CleanUpManagedFieldsForSSAAdoption(ctx context.Context, c client.Client, obj client.Object, ssaManager string) error { 113 // Return if `ssaManager` already owns any fields. 114 if hasFieldsManagedBy(obj, ssaManager) { 115 return nil 116 } 117 118 // Since there is no field managed by `ssaManager` it means that 119 // this is the first time this object is being processed after the controller calling this function 120 // started to use SSA patches. 121 // It is required to clean-up managedFields from entries created by the regular patches. 122 // This will ensure that `ssaManager` will be able to modify the fields that 123 // were originally owned by "manager". 124 base := obj.DeepCopyObject().(client.Object) 125 126 // Remove managedFieldEntry for manager=manager and operation=update to prevent having two managers holding values. 127 originalManagedFields := obj.GetManagedFields() 128 managedFields := make([]metav1.ManagedFieldsEntry, 0, len(originalManagedFields)) 129 for i := range originalManagedFields { 130 if originalManagedFields[i].Manager == classicManager && 131 originalManagedFields[i].Operation == metav1.ManagedFieldsOperationUpdate { 132 continue 133 } 134 managedFields = append(managedFields, originalManagedFields[i]) 135 } 136 137 // Add a seeding managedFieldEntry for SSA executed by the management controller, to prevent SSA to create/infer 138 // a default managedFieldEntry when the first SSA is applied. 139 // More specifically, if an existing object doesn't have managedFields when applying the first SSA the API server 140 // creates an entry with operation=Update (kind of guessing where the object comes from), but this entry ends up 141 // acting as a co-ownership and we want to prevent this. 142 // NOTE: fieldV1Map cannot be empty, so we add metadata.name which will be cleaned up at the first SSA patch. 143 fieldV1Map := map[string]interface{}{ 144 "f:metadata": map[string]interface{}{ 145 "f:name": map[string]interface{}{}, 146 }, 147 } 148 fieldV1, err := json.Marshal(fieldV1Map) 149 if err != nil { 150 return errors.Wrap(err, "failed to create seeding fieldV1Map for cleaning up legacy managed fields") 151 } 152 now := metav1.Now() 153 gvk, err := apiutil.GVKForObject(obj, c.Scheme()) 154 if err != nil { 155 return errors.Wrapf(err, "failed to get GroupVersionKind of object %s", klog.KObj(obj)) 156 } 157 managedFields = append(managedFields, metav1.ManagedFieldsEntry{ 158 Manager: ssaManager, 159 Operation: metav1.ManagedFieldsOperationApply, 160 APIVersion: gvk.GroupVersion().String(), 161 Time: &now, 162 FieldsType: "FieldsV1", 163 FieldsV1: &metav1.FieldsV1{Raw: fieldV1}, 164 }) 165 166 obj.SetManagedFields(managedFields) 167 168 return c.Patch(ctx, obj, client.MergeFrom(base)) 169 } 170 171 // hasFieldsManagedBy returns true if any of the fields in obj are managed by manager. 172 func hasFieldsManagedBy(obj client.Object, manager string) bool { 173 managedFields := obj.GetManagedFields() 174 for _, mf := range managedFields { 175 if mf.Manager == manager { 176 return true 177 } 178 } 179 return false 180 }