sigs.k8s.io/cluster-api@v1.6.3/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  }