...

Source file src/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedmanager.go

Documentation: k8s.io/apimachinery/pkg/util/managedfields/internal

     1  /*
     2  Copyright 2020 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 internal
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  
    23  	"k8s.io/apimachinery/pkg/api/meta"
    24  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	"k8s.io/apimachinery/pkg/runtime/schema"
    27  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    28  	"sigs.k8s.io/structured-merge-diff/v4/merge"
    29  )
    30  
    31  type lastAppliedManager struct {
    32  	fieldManager    Manager
    33  	typeConverter   TypeConverter
    34  	objectConverter runtime.ObjectConvertor
    35  	groupVersion    schema.GroupVersion
    36  }
    37  
    38  var _ Manager = &lastAppliedManager{}
    39  
    40  // NewLastAppliedManager converts the client-side apply annotation to
    41  // server-side apply managed fields
    42  func NewLastAppliedManager(fieldManager Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, groupVersion schema.GroupVersion) Manager {
    43  	return &lastAppliedManager{
    44  		fieldManager:    fieldManager,
    45  		typeConverter:   typeConverter,
    46  		objectConverter: objectConverter,
    47  		groupVersion:    groupVersion,
    48  	}
    49  }
    50  
    51  // Update implements Manager.
    52  func (f *lastAppliedManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
    53  	return f.fieldManager.Update(liveObj, newObj, managed, manager)
    54  }
    55  
    56  // Apply will consider the last-applied annotation
    57  // for upgrading an object managed by client-side apply to server-side apply
    58  // without conflicts.
    59  func (f *lastAppliedManager) Apply(liveObj, newObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
    60  	newLiveObj, newManaged, newErr := f.fieldManager.Apply(liveObj, newObj, managed, manager, force)
    61  	// Upgrade the client-side apply annotation only from kubectl server-side-apply.
    62  	// To opt-out of this behavior, users may specify a different field manager.
    63  	if manager != "kubectl" {
    64  		return newLiveObj, newManaged, newErr
    65  	}
    66  
    67  	// Check if we have conflicts
    68  	if newErr == nil {
    69  		return newLiveObj, newManaged, newErr
    70  	}
    71  	conflicts, ok := newErr.(merge.Conflicts)
    72  	if !ok {
    73  		return newLiveObj, newManaged, newErr
    74  	}
    75  	conflictSet := conflictsToSet(conflicts)
    76  
    77  	// Check if conflicts are allowed due to client-side apply,
    78  	// and if so, then force apply
    79  	allowedConflictSet, err := f.allowedConflictsFromLastApplied(liveObj)
    80  	if err != nil {
    81  		return newLiveObj, newManaged, newErr
    82  	}
    83  	if !conflictSet.Difference(allowedConflictSet).Empty() {
    84  		newConflicts := conflictsDifference(conflicts, allowedConflictSet)
    85  		return newLiveObj, newManaged, newConflicts
    86  	}
    87  
    88  	return f.fieldManager.Apply(liveObj, newObj, managed, manager, true)
    89  }
    90  
    91  func (f *lastAppliedManager) allowedConflictsFromLastApplied(liveObj runtime.Object) (*fieldpath.Set, error) {
    92  	var accessor, err = meta.Accessor(liveObj)
    93  	if err != nil {
    94  		panic(fmt.Sprintf("couldn't get accessor: %v", err))
    95  	}
    96  
    97  	// If there is no client-side apply annotation, then there is nothing to do
    98  	var annotations = accessor.GetAnnotations()
    99  	if annotations == nil {
   100  		return nil, fmt.Errorf("no last applied annotation")
   101  	}
   102  	var lastApplied, ok = annotations[LastAppliedConfigAnnotation]
   103  	if !ok || lastApplied == "" {
   104  		return nil, fmt.Errorf("no last applied annotation")
   105  	}
   106  
   107  	liveObjVersioned, err := f.objectConverter.ConvertToVersion(liveObj, f.groupVersion)
   108  	if err != nil {
   109  		return nil, fmt.Errorf("failed to convert live obj to versioned: %v", err)
   110  	}
   111  
   112  	liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
   113  	if err != nil {
   114  		return nil, fmt.Errorf("failed to convert live obj to typed: %v", err)
   115  	}
   116  
   117  	var lastAppliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}}
   118  	err = json.Unmarshal([]byte(lastApplied), lastAppliedObj)
   119  	if err != nil {
   120  		return nil, fmt.Errorf("failed to decode last applied obj: %v in '%s'", err, lastApplied)
   121  	}
   122  
   123  	if lastAppliedObj.GetAPIVersion() != f.groupVersion.String() {
   124  		return nil, fmt.Errorf("expected version of last applied to match live object '%s', but got '%s': %v", f.groupVersion.String(), lastAppliedObj.GetAPIVersion(), err)
   125  	}
   126  
   127  	lastAppliedObjTyped, err := f.typeConverter.ObjectToTyped(lastAppliedObj)
   128  	if err != nil {
   129  		return nil, fmt.Errorf("failed to convert last applied to typed: %v", err)
   130  	}
   131  
   132  	lastAppliedObjFieldSet, err := lastAppliedObjTyped.ToFieldSet()
   133  	if err != nil {
   134  		return nil, fmt.Errorf("failed to create fieldset for last applied object: %v", err)
   135  	}
   136  
   137  	comparison, err := lastAppliedObjTyped.Compare(liveObjTyped)
   138  	if err != nil {
   139  		return nil, fmt.Errorf("failed to compare last applied object and live object: %v", err)
   140  	}
   141  
   142  	// Remove fields in last applied that are different, added, or missing in
   143  	// the live object.
   144  	// Because last-applied fields don't match the live object fields,
   145  	// then we don't own these fields.
   146  	lastAppliedObjFieldSet = lastAppliedObjFieldSet.
   147  		Difference(comparison.Modified).
   148  		Difference(comparison.Added).
   149  		Difference(comparison.Removed)
   150  
   151  	return lastAppliedObjFieldSet, nil
   152  }
   153  
   154  // TODO: replace with merge.Conflicts.ToSet()
   155  func conflictsToSet(conflicts merge.Conflicts) *fieldpath.Set {
   156  	conflictSet := fieldpath.NewSet()
   157  	for _, conflict := range []merge.Conflict(conflicts) {
   158  		conflictSet.Insert(conflict.Path)
   159  	}
   160  	return conflictSet
   161  }
   162  
   163  func conflictsDifference(conflicts merge.Conflicts, s *fieldpath.Set) merge.Conflicts {
   164  	newConflicts := []merge.Conflict{}
   165  	for _, conflict := range []merge.Conflict(conflicts) {
   166  		if !s.Has(conflict.Path) {
   167  			newConflicts = append(newConflicts, conflict)
   168  		}
   169  	}
   170  	return newConflicts
   171  }
   172  

View as plain text