...

Source file src/k8s.io/client-go/util/csaupgrade/upgrade.go

Documentation: k8s.io/client-go/util/csaupgrade

     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  }
   335  

View as plain text