sigs.k8s.io/cluster-api@v1.7.1/exp/addons/internal/controllers/clusterresourceset_scope.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 controllers
    18  
    19  import (
    20  	"context"
    21  
    22  	"github.com/pkg/errors"
    23  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    24  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    25  	kerrors "k8s.io/apimachinery/pkg/util/errors"
    26  	"k8s.io/klog/v2"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  
    29  	addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1"
    30  )
    31  
    32  // resourceReconcileScope contains the scope for a CRS's resource
    33  // reconciliation request.
    34  type resourceReconcileScope interface {
    35  	// needsApply determines if a resource needs to be applied to the target cluster
    36  	// based on the strategy.
    37  	needsApply() bool
    38  	// apply reconciles all objects defined by the resource following the proper strategy for the CRS.
    39  	apply(ctx context.Context, c client.Client) error
    40  	// hash returns a computed hash of the defined objects in the resource. It is consistent
    41  	// between runs.
    42  	hash() string
    43  }
    44  
    45  func reconcileScopeForResource(
    46  	crs *addonsv1.ClusterResourceSet,
    47  	resourceRef addonsv1.ResourceRef,
    48  	resourceSetBinding *addonsv1.ResourceSetBinding,
    49  	resource *unstructured.Unstructured,
    50  ) (resourceReconcileScope, error) {
    51  	normalizedData, err := normalizeData(resource)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	objs, err := objsFromYamlData(normalizedData)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	return newResourceReconcileScope(crs, resourceRef, resourceSetBinding, normalizedData, objs), nil
    62  }
    63  
    64  func newResourceReconcileScope(
    65  	clusterResourceSet *addonsv1.ClusterResourceSet,
    66  	resourceRef addonsv1.ResourceRef,
    67  	resourceSetBinding *addonsv1.ResourceSetBinding,
    68  	normalizedData [][]byte,
    69  	objs []unstructured.Unstructured,
    70  ) resourceReconcileScope {
    71  	base := baseResourceReconcileScope{
    72  		clusterResourceSet: clusterResourceSet,
    73  		resourceRef:        resourceRef,
    74  		resourceSetBinding: resourceSetBinding,
    75  		data:               normalizedData,
    76  		normalizedObjs:     objs,
    77  		computedHash:       computeHash(normalizedData),
    78  	}
    79  
    80  	switch addonsv1.ClusterResourceSetStrategy(clusterResourceSet.Spec.Strategy) {
    81  	case addonsv1.ClusterResourceSetStrategyApplyOnce:
    82  		return &reconcileApplyOnceScope{base}
    83  	case addonsv1.ClusterResourceSetStrategyReconcile:
    84  		return &reconcileStrategyScope{base}
    85  	default:
    86  		return nil
    87  	}
    88  }
    89  
    90  type baseResourceReconcileScope struct {
    91  	clusterResourceSet *addonsv1.ClusterResourceSet
    92  	resourceRef        addonsv1.ResourceRef
    93  	resourceSetBinding *addonsv1.ResourceSetBinding
    94  	normalizedObjs     []unstructured.Unstructured
    95  	data               [][]byte
    96  	computedHash       string
    97  }
    98  
    99  func (b baseResourceReconcileScope) objs() []unstructured.Unstructured {
   100  	return b.normalizedObjs
   101  }
   102  
   103  func (b baseResourceReconcileScope) hash() string {
   104  	return b.computedHash
   105  }
   106  
   107  type reconcileStrategyScope struct {
   108  	baseResourceReconcileScope
   109  }
   110  
   111  func (r *reconcileStrategyScope) needsApply() bool {
   112  	resourceBinding := r.resourceSetBinding.GetResource(r.resourceRef)
   113  
   114  	return resourceBinding == nil || !resourceBinding.Applied || resourceBinding.Hash != r.computedHash
   115  }
   116  
   117  func (r *reconcileStrategyScope) apply(ctx context.Context, c client.Client) error {
   118  	return apply(ctx, c, r.applyObj, r.objs())
   119  }
   120  
   121  func (r *reconcileStrategyScope) applyObj(ctx context.Context, c client.Client, obj *unstructured.Unstructured) error {
   122  	currentObj := &unstructured.Unstructured{}
   123  	currentObj.SetAPIVersion(obj.GetAPIVersion())
   124  	currentObj.SetKind(obj.GetKind())
   125  	err := c.Get(ctx, client.ObjectKeyFromObject(obj), currentObj)
   126  	if apierrors.IsNotFound(err) {
   127  		return createUnstructured(ctx, c, obj)
   128  	}
   129  	if err != nil {
   130  		return errors.Wrapf(
   131  			err,
   132  			"reading object %s %s",
   133  			obj.GroupVersionKind(),
   134  			klog.KObj(obj),
   135  		)
   136  	}
   137  
   138  	patch := client.MergeFrom(currentObj.DeepCopy())
   139  	obj.SetResourceVersion(currentObj.GetResourceVersion())
   140  	if err = c.Patch(ctx, obj, patch); err != nil {
   141  		return errors.Wrapf(
   142  			err,
   143  			"patching object %s %s",
   144  			obj.GroupVersionKind(),
   145  			klog.KObj(obj),
   146  		)
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  type reconcileApplyOnceScope struct {
   153  	baseResourceReconcileScope
   154  }
   155  
   156  func (r *reconcileApplyOnceScope) needsApply() bool {
   157  	return !r.resourceSetBinding.IsApplied(r.resourceRef)
   158  }
   159  
   160  func (r *reconcileApplyOnceScope) apply(ctx context.Context, c client.Client) error {
   161  	return apply(ctx, c, r.applyObj, r.objs())
   162  }
   163  
   164  func (r *reconcileApplyOnceScope) applyObj(ctx context.Context, c client.Client, obj *unstructured.Unstructured) error {
   165  	// The create call is idempotent, so if the object already exists
   166  	// then do not consider it to be an error.
   167  	if err := createUnstructured(ctx, c, obj); err != nil && !apierrors.IsAlreadyExists(err) {
   168  		return err
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  type applyObj func(ctx context.Context, c client.Client, obj *unstructured.Unstructured) error
   175  
   176  // apply reconciles unstructured objects using applyObj and aggreates the error if present.
   177  func apply(ctx context.Context, c client.Client, applyObj applyObj, objs []unstructured.Unstructured) error {
   178  	errList := []error{}
   179  	for i := range objs {
   180  		if err := applyObj(ctx, c, &objs[i]); err != nil {
   181  			errList = append(errList, err)
   182  		}
   183  	}
   184  
   185  	return kerrors.NewAggregate(errList)
   186  }