sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/cluster/structuredmerge/serversidepathhelper.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 structuredmerge
    18  
    19  import (
    20  	"context"
    21  
    22  	"github.com/pkg/errors"
    23  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    24  	ctrl "sigs.k8s.io/controller-runtime"
    25  	"sigs.k8s.io/controller-runtime/pkg/client"
    26  
    27  	"sigs.k8s.io/cluster-api/internal/util/ssa"
    28  	"sigs.k8s.io/cluster-api/util"
    29  )
    30  
    31  // TopologyManagerName is the manager name in managed fields for the topology controller.
    32  const TopologyManagerName = "capi-topology"
    33  
    34  type serverSidePatchHelper struct {
    35  	client         client.Client
    36  	modified       *unstructured.Unstructured
    37  	hasChanges     bool
    38  	hasSpecChanges bool
    39  }
    40  
    41  // NewServerSidePatchHelper returns a new PatchHelper using server side apply.
    42  func NewServerSidePatchHelper(ctx context.Context, original, modified client.Object, c client.Client, ssaCache ssa.Cache, opts ...HelperOption) (PatchHelper, error) {
    43  	// Create helperOptions for filtering the original and modified objects to the desired intent.
    44  	helperOptions := newHelperOptions(modified, opts...)
    45  
    46  	// If required, convert the original and modified objects to unstructured and filter out all the information
    47  	// not relevant for the topology controller.
    48  
    49  	var originalUnstructured *unstructured.Unstructured
    50  	if !util.IsNil(original) {
    51  		originalUnstructured = &unstructured.Unstructured{}
    52  		switch original.(type) {
    53  		case *unstructured.Unstructured:
    54  			originalUnstructured = original.DeepCopyObject().(*unstructured.Unstructured)
    55  		default:
    56  			if err := c.Scheme().Convert(original, originalUnstructured, nil); err != nil {
    57  				return nil, errors.Wrap(err, "failed to convert original object to Unstructured")
    58  			}
    59  		}
    60  	}
    61  
    62  	modifiedUnstructured := &unstructured.Unstructured{}
    63  	switch modified.(type) {
    64  	case *unstructured.Unstructured:
    65  		modifiedUnstructured = modified.DeepCopyObject().(*unstructured.Unstructured)
    66  	default:
    67  		if err := c.Scheme().Convert(modified, modifiedUnstructured, nil); err != nil {
    68  			return nil, errors.Wrap(err, "failed to convert modified object to Unstructured")
    69  		}
    70  	}
    71  
    72  	// Filter the modifiedUnstructured object to only contain changes intendet to be done.
    73  	// The originalUnstructured object will be filtered in dryRunSSAPatch using other options.
    74  	ssa.FilterObject(modifiedUnstructured, &helperOptions.FilterObjectInput)
    75  
    76  	// Carry over uid to match the intent to:
    77  	// * create (uid==""):
    78  	//   * if object doesn't exist => create
    79  	//   * if object already exists => update
    80  	// * update (uid!=""):
    81  	//   * if object doesn't exist => fail
    82  	//   * if object already exists => update
    83  	//   * This allows us to enforce that an update doesn't create an object which has been deleted.
    84  	modifiedUnstructured.SetUID("")
    85  	if originalUnstructured != nil {
    86  		modifiedUnstructured.SetUID(originalUnstructured.GetUID())
    87  	}
    88  
    89  	// Determine if the intent defined in the modified object is going to trigger
    90  	// an actual change when running server side apply, and if this change might impact the object spec or not.
    91  	var hasChanges, hasSpecChanges bool
    92  	switch {
    93  	case util.IsNil(original):
    94  		hasChanges, hasSpecChanges = true, true
    95  	default:
    96  		var err error
    97  		hasChanges, hasSpecChanges, err = dryRunSSAPatch(ctx, &dryRunSSAPatchInput{
    98  			client:               c,
    99  			ssaCache:             ssaCache,
   100  			originalUnstructured: originalUnstructured,
   101  			modifiedUnstructured: modifiedUnstructured.DeepCopy(),
   102  			helperOptions:        helperOptions,
   103  		})
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  	}
   108  
   109  	return &serverSidePatchHelper{
   110  		client:         c,
   111  		modified:       modifiedUnstructured,
   112  		hasChanges:     hasChanges,
   113  		hasSpecChanges: hasSpecChanges,
   114  	}, nil
   115  }
   116  
   117  // HasSpecChanges return true if the patch has changes to the spec field.
   118  func (h *serverSidePatchHelper) HasSpecChanges() bool {
   119  	return h.hasSpecChanges
   120  }
   121  
   122  // HasChanges return true if the patch has changes.
   123  func (h *serverSidePatchHelper) HasChanges() bool {
   124  	return h.hasChanges
   125  }
   126  
   127  // Patch will server side apply the current intent (the modified object.
   128  func (h *serverSidePatchHelper) Patch(ctx context.Context) error {
   129  	if !h.HasChanges() {
   130  		return nil
   131  	}
   132  
   133  	log := ctrl.LoggerFrom(ctx)
   134  	log.V(5).Info("Patching object", "Intent", h.modified)
   135  
   136  	options := []client.PatchOption{
   137  		client.FieldOwner(TopologyManagerName),
   138  		// NOTE: we are using force ownership so in case of conflicts the topology controller
   139  		// overwrite values and become sole manager.
   140  		client.ForceOwnership,
   141  	}
   142  	return h.client.Patch(ctx, h.modified, client.Apply, options...)
   143  }