sigs.k8s.io/cluster-api@v1.7.1/exp/addons/internal/controllers/clusterresourceset_controller.go (about)

     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 controllers
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/pkg/errors"
    25  	corev1 "k8s.io/api/core/v1"
    26  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    29  	"k8s.io/apimachinery/pkg/labels"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	kerrors "k8s.io/apimachinery/pkg/util/errors"
    32  	"k8s.io/klog/v2"
    33  	ctrl "sigs.k8s.io/controller-runtime"
    34  	"sigs.k8s.io/controller-runtime/pkg/builder"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
    37  	"sigs.k8s.io/controller-runtime/pkg/controller"
    38  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    39  	"sigs.k8s.io/controller-runtime/pkg/handler"
    40  
    41  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    42  	"sigs.k8s.io/cluster-api/controllers/remote"
    43  	addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1"
    44  	resourcepredicates "sigs.k8s.io/cluster-api/exp/addons/internal/controllers/predicates"
    45  	"sigs.k8s.io/cluster-api/util"
    46  	"sigs.k8s.io/cluster-api/util/conditions"
    47  	"sigs.k8s.io/cluster-api/util/patch"
    48  	"sigs.k8s.io/cluster-api/util/predicates"
    49  )
    50  
    51  // ErrSecretTypeNotSupported signals that a Secret is not supported.
    52  var ErrSecretTypeNotSupported = errors.New("unsupported secret type")
    53  
    54  // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;patch
    55  // +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;patch
    56  // +kubebuilder:rbac:groups=addons.cluster.x-k8s.io,resources=*,verbs=get;list;watch;create;update;patch;delete
    57  // +kubebuilder:rbac:groups=addons.cluster.x-k8s.io,resources=clusterresourcesets/status;clusterresourcesets/finalizers,verbs=get;update;patch
    58  
    59  // ClusterResourceSetReconciler reconciles a ClusterResourceSet object.
    60  type ClusterResourceSetReconciler struct {
    61  	Client  client.Client
    62  	Tracker *remote.ClusterCacheTracker
    63  
    64  	// WatchFilterValue is the label value used to filter events prior to reconciliation.
    65  	WatchFilterValue string
    66  }
    67  
    68  func (r *ClusterResourceSetReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
    69  	err := ctrl.NewControllerManagedBy(mgr).
    70  		For(&addonsv1.ClusterResourceSet{}).
    71  		Watches(
    72  			&clusterv1.Cluster{},
    73  			handler.EnqueueRequestsFromMapFunc(r.clusterToClusterResourceSet),
    74  		).
    75  		WatchesMetadata(
    76  			&corev1.ConfigMap{},
    77  			handler.EnqueueRequestsFromMapFunc(r.resourceToClusterResourceSet),
    78  			builder.WithPredicates(
    79  				resourcepredicates.ResourceCreateOrUpdate(ctrl.LoggerFrom(ctx)),
    80  			),
    81  		).
    82  		WatchesMetadata(
    83  			&corev1.Secret{},
    84  			handler.EnqueueRequestsFromMapFunc(r.resourceToClusterResourceSet),
    85  			builder.WithPredicates(
    86  				resourcepredicates.ResourceCreateOrUpdate(ctrl.LoggerFrom(ctx)),
    87  			),
    88  		).
    89  		WithOptions(options).
    90  		WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(ctrl.LoggerFrom(ctx), r.WatchFilterValue)).
    91  		Complete(r)
    92  	if err != nil {
    93  		return errors.Wrap(err, "failed setting up with a controller manager")
    94  	}
    95  
    96  	return nil
    97  }
    98  
    99  func (r *ClusterResourceSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
   100  	log := ctrl.LoggerFrom(ctx)
   101  
   102  	// Fetch the ClusterResourceSet instance.
   103  	clusterResourceSet := &addonsv1.ClusterResourceSet{}
   104  	if err := r.Client.Get(ctx, req.NamespacedName, clusterResourceSet); err != nil {
   105  		if apierrors.IsNotFound(err) {
   106  			// Object not found, return.  Created objects are automatically garbage collected.
   107  			// For additional cleanup logic use finalizers.
   108  			return ctrl.Result{}, nil
   109  		}
   110  		// Error reading the object - requeue the request.
   111  		return ctrl.Result{}, err
   112  	}
   113  
   114  	// Initialize the patch helper.
   115  	patchHelper, err := patch.NewHelper(clusterResourceSet, r.Client)
   116  	if err != nil {
   117  		return ctrl.Result{}, err
   118  	}
   119  
   120  	defer func() {
   121  		// Always attempt to Patch the ClusterResourceSet object and status after each reconciliation.
   122  		if err := patchHelper.Patch(ctx, clusterResourceSet, patch.WithStatusObservedGeneration{}); err != nil {
   123  			reterr = kerrors.NewAggregate([]error{reterr, err})
   124  		}
   125  	}()
   126  
   127  	clusters, err := r.getClustersByClusterResourceSetSelector(ctx, clusterResourceSet)
   128  	if err != nil {
   129  		log.Error(err, "Failed fetching clusters that matches ClusterResourceSet labels", "ClusterResourceSet", klog.KObj(clusterResourceSet))
   130  		conditions.MarkFalse(clusterResourceSet, addonsv1.ResourcesAppliedCondition, addonsv1.ClusterMatchFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
   131  		return ctrl.Result{}, err
   132  	}
   133  
   134  	// Handle deletion reconciliation loop.
   135  	if !clusterResourceSet.ObjectMeta.DeletionTimestamp.IsZero() {
   136  		return ctrl.Result{}, r.reconcileDelete(ctx, clusters, clusterResourceSet)
   137  	}
   138  
   139  	// Add finalizer first if not set to avoid the race condition between init and delete.
   140  	// Note: Finalizers in general can only be added when the deletionTimestamp is not set.
   141  	if !controllerutil.ContainsFinalizer(clusterResourceSet, addonsv1.ClusterResourceSetFinalizer) {
   142  		controllerutil.AddFinalizer(clusterResourceSet, addonsv1.ClusterResourceSetFinalizer)
   143  		return ctrl.Result{}, nil
   144  	}
   145  
   146  	errs := []error{}
   147  	errClusterLockedOccurred := false
   148  	for _, cluster := range clusters {
   149  		if err := r.ApplyClusterResourceSet(ctx, cluster, clusterResourceSet); err != nil {
   150  			// Requeue if the reconcile failed because the ClusterCacheTracker was locked for
   151  			// the current cluster because of concurrent access.
   152  			if errors.Is(err, remote.ErrClusterLocked) {
   153  				log.V(5).Info("Requeuing because another worker has the lock on the ClusterCacheTracker")
   154  				errClusterLockedOccurred = true
   155  			} else {
   156  				// Append the error if the error is not ErrClusterLocked.
   157  				errs = append(errs, err)
   158  			}
   159  		}
   160  	}
   161  
   162  	// Return an aggregated error if errors occurred.
   163  	if len(errs) > 0 {
   164  		return ctrl.Result{}, kerrors.NewAggregate(errs)
   165  	}
   166  
   167  	// Requeue if ErrClusterLocked was returned for one of the clusters.
   168  	if errClusterLockedOccurred {
   169  		// Requeue after a minute to not end up in exponential delayed requeue which
   170  		// could take up to 16m40s.
   171  		return ctrl.Result{RequeueAfter: time.Minute}, nil
   172  	}
   173  
   174  	return ctrl.Result{}, nil
   175  }
   176  
   177  // reconcileDelete removes the deleted ClusterResourceSet from all the ClusterResourceSetBindings it is added to.
   178  func (r *ClusterResourceSetReconciler) reconcileDelete(ctx context.Context, clusters []*clusterv1.Cluster, crs *addonsv1.ClusterResourceSet) error {
   179  	for _, cluster := range clusters {
   180  		log := ctrl.LoggerFrom(ctx, "Cluster", klog.KObj(cluster))
   181  
   182  		clusterResourceSetBinding := &addonsv1.ClusterResourceSetBinding{}
   183  		clusterResourceSetBindingKey := client.ObjectKey{
   184  			Namespace: cluster.Namespace,
   185  			Name:      cluster.Name,
   186  		}
   187  		if err := r.Client.Get(ctx, clusterResourceSetBindingKey, clusterResourceSetBinding); err != nil {
   188  			if !apierrors.IsNotFound(err) {
   189  				return errors.Wrapf(err, "failed to get ClusterResourceSetBinding during ClusterResourceSet deletion")
   190  			}
   191  			controllerutil.RemoveFinalizer(crs, addonsv1.ClusterResourceSetFinalizer)
   192  			return nil
   193  		}
   194  
   195  		// Initialize the patch helper.
   196  		patchHelper, err := patch.NewHelper(clusterResourceSetBinding, r.Client)
   197  		if err != nil {
   198  			return err
   199  		}
   200  
   201  		clusterResourceSetBinding.RemoveBinding(crs)
   202  		clusterResourceSetBinding.OwnerReferences = util.RemoveOwnerRef(clusterResourceSetBinding.GetOwnerReferences(), metav1.OwnerReference{
   203  			APIVersion: addonsv1.GroupVersion.String(),
   204  			Kind:       "ClusterResourceSet",
   205  			Name:       crs.Name,
   206  		})
   207  
   208  		// If CRS list is empty in the binding, delete the binding else
   209  		// attempt to Patch the ClusterResourceSetBinding object after delete reconciliation if there is at least 1 binding left.
   210  		if len(clusterResourceSetBinding.Spec.Bindings) == 0 {
   211  			if r.Client.Delete(ctx, clusterResourceSetBinding) != nil {
   212  				log.Error(err, "failed to delete empty ClusterResourceSetBinding")
   213  			}
   214  		} else if err := patchHelper.Patch(ctx, clusterResourceSetBinding); err != nil {
   215  			return err
   216  		}
   217  	}
   218  
   219  	controllerutil.RemoveFinalizer(crs, addonsv1.ClusterResourceSetFinalizer)
   220  	return nil
   221  }
   222  
   223  // getClustersByClusterResourceSetSelector fetches Clusters matched by the ClusterResourceSet's label selector that are in the same namespace as the ClusterResourceSet object.
   224  func (r *ClusterResourceSetReconciler) getClustersByClusterResourceSetSelector(ctx context.Context, clusterResourceSet *addonsv1.ClusterResourceSet) ([]*clusterv1.Cluster, error) {
   225  	log := ctrl.LoggerFrom(ctx)
   226  
   227  	clusterList := &clusterv1.ClusterList{}
   228  	selector, err := metav1.LabelSelectorAsSelector(&clusterResourceSet.Spec.ClusterSelector)
   229  	if err != nil {
   230  		return nil, errors.Wrap(err, "unable to convert selector")
   231  	}
   232  
   233  	// If a ClusterResourceSet has a nil or empty selector, it should match nothing, not everything.
   234  	if selector.Empty() {
   235  		log.Info("Empty ClusterResourceSet selector: No clusters are selected.")
   236  		return nil, nil
   237  	}
   238  
   239  	if err := r.Client.List(ctx, clusterList, client.InNamespace(clusterResourceSet.Namespace), client.MatchingLabelsSelector{Selector: selector}); err != nil {
   240  		return nil, errors.Wrap(err, "failed to list clusters")
   241  	}
   242  
   243  	clusters := []*clusterv1.Cluster{}
   244  	for i := range clusterList.Items {
   245  		c := &clusterList.Items[i]
   246  		if c.DeletionTimestamp.IsZero() {
   247  			clusters = append(clusters, c)
   248  		}
   249  	}
   250  	return clusters, nil
   251  }
   252  
   253  // ApplyClusterResourceSet applies resources in a ClusterResourceSet to a Cluster. Once applied, a record will be added to the
   254  // cluster's ClusterResourceSetBinding.
   255  // In ApplyOnce strategy, resources are applied only once to a particular cluster. ClusterResourceSetBinding is used to check if a resource is applied before.
   256  // It applies resources best effort and continue on scenarios like: unsupported resource types, failure during creation, missing resources.
   257  // In Reconcile strategy, resources are re-applied to a particular cluster when their definition changes. The hash in ClusterResourceSetBinding is used to check
   258  // if a resource has changed or not.
   259  // TODO: If a resource already exists in the cluster but not applied by ClusterResourceSet, the resource will be updated ?
   260  func (r *ClusterResourceSetReconciler) ApplyClusterResourceSet(ctx context.Context, cluster *clusterv1.Cluster, clusterResourceSet *addonsv1.ClusterResourceSet) error {
   261  	log := ctrl.LoggerFrom(ctx, "Cluster", klog.KObj(cluster))
   262  	ctx = ctrl.LoggerInto(ctx, log)
   263  
   264  	remoteClient, err := r.Tracker.GetClient(ctx, util.ObjectKey(cluster))
   265  	if err != nil {
   266  		conditions.MarkFalse(clusterResourceSet, addonsv1.ResourcesAppliedCondition, addonsv1.RemoteClusterClientFailedReason, clusterv1.ConditionSeverityError, err.Error())
   267  		return err
   268  	}
   269  
   270  	// Ensure that the Kubernetes API Server service has been created in the remote cluster before applying the ClusterResourceSet to avoid service IP conflict.
   271  	// This action is required when the remote cluster Kubernetes version is lower than v1.25.
   272  	// TODO: Remove this action once CAPI no longer supports Kubernetes versions below v1.25. See: https://github.com/kubernetes-sigs/cluster-api/issues/7804
   273  	if err = ensureKubernetesServiceCreated(ctx, remoteClient); err != nil {
   274  		return errors.Wrapf(err, "failed to retrieve the Service for Kubernetes API Server of the cluster %s/%s", cluster.Namespace, cluster.Name)
   275  	}
   276  
   277  	// Get ClusterResourceSetBinding object for the cluster.
   278  	clusterResourceSetBinding, err := r.getOrCreateClusterResourceSetBinding(ctx, cluster, clusterResourceSet)
   279  	if err != nil {
   280  		return err
   281  	}
   282  
   283  	// Initialize the patch helper.
   284  	patchHelper, err := patch.NewHelper(clusterResourceSetBinding, r.Client)
   285  	if err != nil {
   286  		return err
   287  	}
   288  
   289  	defer func() {
   290  		// Always attempt to Patch the ClusterResourceSetBinding object after each reconciliation.
   291  		if err := patchHelper.Patch(ctx, clusterResourceSetBinding); err != nil {
   292  			log.Error(err, "failed to patch config")
   293  		}
   294  	}()
   295  
   296  	// Ensure that the owner references are set on the ClusterResourceSetBinding.
   297  	clusterResourceSetBinding.SetOwnerReferences(util.EnsureOwnerRef(clusterResourceSetBinding.GetOwnerReferences(), metav1.OwnerReference{
   298  		APIVersion: addonsv1.GroupVersion.String(),
   299  		Kind:       "ClusterResourceSet",
   300  		Name:       clusterResourceSet.Name,
   301  		UID:        clusterResourceSet.UID,
   302  	}))
   303  	errList := []error{}
   304  	resourceSetBinding := clusterResourceSetBinding.GetOrCreateBinding(clusterResourceSet)
   305  
   306  	// Iterate all resources and apply them to the cluster and update the resource status in the ClusterResourceSetBinding object.
   307  	for _, resource := range clusterResourceSet.Spec.Resources {
   308  		unstructuredObj, err := r.getResource(ctx, resource, cluster.GetNamespace())
   309  		if err != nil {
   310  			if err == ErrSecretTypeNotSupported {
   311  				conditions.MarkFalse(clusterResourceSet, addonsv1.ResourcesAppliedCondition, addonsv1.WrongSecretTypeReason, clusterv1.ConditionSeverityWarning, err.Error())
   312  			} else {
   313  				conditions.MarkFalse(clusterResourceSet, addonsv1.ResourcesAppliedCondition, addonsv1.RetrievingResourceFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
   314  
   315  				// Continue without adding the error to the aggregate if we can't find the resource.
   316  				if apierrors.IsNotFound(err) {
   317  					continue
   318  				}
   319  			}
   320  			errList = append(errList, err)
   321  			continue
   322  		}
   323  
   324  		// Ensure an ownerReference to the clusterResourceSet is on the resource.
   325  		if err := r.ensureResourceOwnerRef(ctx, clusterResourceSet, unstructuredObj); err != nil {
   326  			log.Error(err, "Failed to add ClusterResourceSet as resource owner reference",
   327  				"Resource type", unstructuredObj.GetKind(), "Resource name", unstructuredObj.GetName())
   328  			errList = append(errList, err)
   329  		}
   330  
   331  		resourceScope, err := reconcileScopeForResource(clusterResourceSet, resource, resourceSetBinding, unstructuredObj)
   332  		if err != nil {
   333  			resourceSetBinding.SetBinding(addonsv1.ResourceBinding{
   334  				ResourceRef:     resource,
   335  				Hash:            "",
   336  				Applied:         false,
   337  				LastAppliedTime: &metav1.Time{Time: time.Now().UTC()},
   338  			})
   339  
   340  			errList = append(errList, err)
   341  			continue
   342  		}
   343  
   344  		if !resourceScope.needsApply() {
   345  			continue
   346  		}
   347  
   348  		// Set status in ClusterResourceSetBinding in case of early continue due to a failure.
   349  		// Set only when resource is retrieved successfully.
   350  		resourceSetBinding.SetBinding(addonsv1.ResourceBinding{
   351  			ResourceRef:     resource,
   352  			Hash:            "",
   353  			Applied:         false,
   354  			LastAppliedTime: &metav1.Time{Time: time.Now().UTC()},
   355  		})
   356  
   357  		// Apply all values in the key-value pair of the resource to the cluster.
   358  		// As there can be multiple key-value pairs in a resource, each value may have multiple objects in it.
   359  		isSuccessful := true
   360  		if err := resourceScope.apply(ctx, remoteClient); err != nil {
   361  			isSuccessful = false
   362  			log.Error(err, "failed to apply ClusterResourceSet resource", "Resource kind", resource.Kind, "Resource name", resource.Name)
   363  			conditions.MarkFalse(clusterResourceSet, addonsv1.ResourcesAppliedCondition, addonsv1.ApplyFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
   364  			errList = append(errList, err)
   365  		}
   366  
   367  		resourceSetBinding.SetBinding(addonsv1.ResourceBinding{
   368  			ResourceRef:     resource,
   369  			Hash:            resourceScope.hash(),
   370  			Applied:         isSuccessful,
   371  			LastAppliedTime: &metav1.Time{Time: time.Now().UTC()},
   372  		})
   373  	}
   374  	if len(errList) > 0 {
   375  		return kerrors.NewAggregate(errList)
   376  	}
   377  
   378  	conditions.MarkTrue(clusterResourceSet, addonsv1.ResourcesAppliedCondition)
   379  
   380  	return nil
   381  }
   382  
   383  // getResource retrieves the requested resource and convert it to unstructured type.
   384  // Unsupported resource kinds are not denied by validation webhook, hence no need to check here.
   385  // Only supports Secrets/Configmaps as resource types and allow using resources in the same namespace with the cluster.
   386  func (r *ClusterResourceSetReconciler) getResource(ctx context.Context, resourceRef addonsv1.ResourceRef, namespace string) (*unstructured.Unstructured, error) {
   387  	resourceName := types.NamespacedName{Name: resourceRef.Name, Namespace: namespace}
   388  
   389  	var resourceInterface interface{}
   390  	switch resourceRef.Kind {
   391  	case string(addonsv1.ConfigMapClusterResourceSetResourceKind):
   392  		resourceConfigMap, err := getConfigMap(ctx, r.Client, resourceName)
   393  		if err != nil {
   394  			return nil, err
   395  		}
   396  
   397  		resourceInterface = resourceConfigMap.DeepCopyObject()
   398  	case string(addonsv1.SecretClusterResourceSetResourceKind):
   399  		resourceSecret, err := getSecret(ctx, r.Client, resourceName)
   400  		if err != nil {
   401  			return nil, err
   402  		}
   403  
   404  		if resourceSecret.Type != addonsv1.ClusterResourceSetSecretType {
   405  			return nil, ErrSecretTypeNotSupported
   406  		}
   407  		resourceInterface = resourceSecret.DeepCopyObject()
   408  	}
   409  
   410  	raw := &unstructured.Unstructured{}
   411  	err := r.Client.Scheme().Convert(resourceInterface, raw, nil)
   412  	if err != nil {
   413  		return nil, err
   414  	}
   415  
   416  	return raw, nil
   417  }
   418  
   419  // ensureResourceOwnerRef adds the ClusterResourceSet as a OwnerReference to the resource.
   420  func (r *ClusterResourceSetReconciler) ensureResourceOwnerRef(ctx context.Context, clusterResourceSet *addonsv1.ClusterResourceSet, resource *unstructured.Unstructured) error {
   421  	obj := resource.DeepCopy()
   422  	patchHelper, err := patch.NewHelper(obj, r.Client)
   423  	if err != nil {
   424  		return err
   425  	}
   426  	newRef := metav1.OwnerReference{
   427  		APIVersion: addonsv1.GroupVersion.String(),
   428  		Kind:       clusterResourceSet.GroupVersionKind().Kind,
   429  		Name:       clusterResourceSet.GetName(),
   430  		UID:        clusterResourceSet.GetUID(),
   431  	}
   432  	obj.SetOwnerReferences(util.EnsureOwnerRef(obj.GetOwnerReferences(), newRef))
   433  	return patchHelper.Patch(ctx, obj)
   434  }
   435  
   436  // clusterToClusterResourceSet is mapper function that maps clusters to ClusterResourceSet.
   437  func (r *ClusterResourceSetReconciler) clusterToClusterResourceSet(ctx context.Context, o client.Object) []ctrl.Request {
   438  	result := []ctrl.Request{}
   439  
   440  	cluster, ok := o.(*clusterv1.Cluster)
   441  	if !ok {
   442  		panic(fmt.Sprintf("Expected a Cluster but got a %T", o))
   443  	}
   444  
   445  	resourceList := &addonsv1.ClusterResourceSetList{}
   446  	if err := r.Client.List(ctx, resourceList, client.InNamespace(cluster.Namespace)); err != nil {
   447  		return nil
   448  	}
   449  
   450  	labels := labels.Set(cluster.GetLabels())
   451  	for i := range resourceList.Items {
   452  		rs := &resourceList.Items[i]
   453  
   454  		selector, err := metav1.LabelSelectorAsSelector(&rs.Spec.ClusterSelector)
   455  		if err != nil {
   456  			return nil
   457  		}
   458  
   459  		// If a ClusterResourceSet has a nil or empty selector, it should match nothing, not everything.
   460  		if selector.Empty() {
   461  			return nil
   462  		}
   463  
   464  		if !selector.Matches(labels) {
   465  			continue
   466  		}
   467  
   468  		name := client.ObjectKey{Namespace: rs.Namespace, Name: rs.Name}
   469  		result = append(result, ctrl.Request{NamespacedName: name})
   470  	}
   471  	return result
   472  }
   473  
   474  // resourceToClusterResourceSet is mapper function that maps resources to ClusterResourceSet.
   475  func (r *ClusterResourceSetReconciler) resourceToClusterResourceSet(ctx context.Context, o client.Object) []ctrl.Request {
   476  	result := []ctrl.Request{}
   477  
   478  	// Add all ClusterResourceSet owners.
   479  	for _, owner := range o.GetOwnerReferences() {
   480  		if owner.Kind == "ClusterResourceSet" {
   481  			name := client.ObjectKey{Namespace: o.GetNamespace(), Name: owner.Name}
   482  			result = append(result, ctrl.Request{NamespacedName: name})
   483  		}
   484  	}
   485  
   486  	// If there is any ClusterResourceSet owner, that means the resource is reconciled before,
   487  	// and existing owners are the only matching ClusterResourceSets to this resource, so no need to return all ClusterResourceSets.
   488  	if len(result) > 0 {
   489  		return result
   490  	}
   491  
   492  	// Only core group is accepted as resources group
   493  	if o.GetObjectKind().GroupVersionKind().Group != "" {
   494  		return result
   495  	}
   496  
   497  	crsList := &addonsv1.ClusterResourceSetList{}
   498  	if err := r.Client.List(ctx, crsList, client.InNamespace(o.GetNamespace())); err != nil {
   499  		return nil
   500  	}
   501  	objKind, err := apiutil.GVKForObject(o, r.Client.Scheme())
   502  	if err != nil {
   503  		return nil
   504  	}
   505  	for _, crs := range crsList.Items {
   506  		for _, resource := range crs.Spec.Resources {
   507  			if resource.Kind == objKind.Kind && resource.Name == o.GetName() {
   508  				name := client.ObjectKey{Namespace: o.GetNamespace(), Name: crs.Name}
   509  				result = append(result, ctrl.Request{NamespacedName: name})
   510  				break
   511  			}
   512  		}
   513  	}
   514  
   515  	return result
   516  }