sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/cluster/objectgraph.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 cluster
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"github.com/pkg/errors"
    25  	corev1 "k8s.io/api/core/v1"
    26  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  
    33  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    34  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    35  	logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
    36  	addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1"
    37  	secretutil "sigs.k8s.io/cluster-api/util/secret"
    38  )
    39  
    40  const clusterTopologyNameKey = "cluster.spec.topology.class"
    41  const clusterResourceSetBindingClusterNameKey = "clusterresourcesetbinding.spec.clustername"
    42  
    43  type empty struct{}
    44  
    45  type ownerReferenceAttributes struct {
    46  	Controller         *bool
    47  	BlockOwnerDeletion *bool
    48  }
    49  
    50  // node defines a node in the Kubernetes object graph that is visited during the discovery phase for the move operation.
    51  type node struct {
    52  	identity corev1.ObjectReference
    53  
    54  	// owners contains the list of nodes that own the current node.
    55  	owners map[*node]ownerReferenceAttributes
    56  
    57  	// softOwners contains the list of nodes that soft-own the current node.
    58  	// E.g. secrets are soft-owned by a cluster via a naming convention, but without an explicit OwnerReference.
    59  	softOwners map[*node]empty
    60  
    61  	// forceMove is set to true if the CRD of this object has the "move" label attached.
    62  	// This ensures the node is moved, regardless of its owner refs.
    63  	forceMove bool
    64  
    65  	// forceMoveHierarchy is set to true if the CRD of this object has the "move-hierarchy" label attached.
    66  	// This ensures the node and it's entire hierarchy of dependants (via owner ref chain) is moved.
    67  	forceMoveHierarchy bool
    68  
    69  	// isGlobal gets set to true if this object is a global resource (no namespace).
    70  	isGlobal bool
    71  
    72  	// isGlobalHierarchy gets set to true if this object is part of a hierarchy of a global resource e.g.
    73  	// a secrets holding credentials for a global identity object.
    74  	// When this flag is true the object should not be deleted from the source cluster.
    75  	isGlobalHierarchy bool
    76  
    77  	// virtual records if this node was discovered indirectly, e.g. by processing an OwnerRef, but not yet observed as a concrete object.
    78  	virtual bool
    79  
    80  	// newID stores the new UID the objects gets once created in the target cluster.
    81  	newUID types.UID
    82  
    83  	// tenant define the list of objects which are tenant for the node, no matter if the node has a direct OwnerReference to the object or if
    84  	// the node is linked to a object indirectly in the OwnerReference chain.
    85  	tenant map[*node]empty
    86  
    87  	// restoreObject holds the object that is referenced when creating a node during fromDirectory from file.
    88  	// the object can then be referenced latter when restoring objects to a target management cluster
    89  	restoreObject *unstructured.Unstructured
    90  
    91  	// additionalInfo captures any additional information about the object the node represents.
    92  	// E.g. for the cluster object we capture information to see if the cluster uses a manged topology
    93  	// and the cluster class used.
    94  	additionalInfo map[string]interface{}
    95  
    96  	// blockingMove is true when the object should prevent a move operation from proceeding as indicated by
    97  	// the presence of the block-move annotation.
    98  	blockingMove bool
    99  }
   100  
   101  type discoveryTypeInfo struct {
   102  	typeMeta           metav1.TypeMeta
   103  	forceMove          bool
   104  	forceMoveHierarchy bool
   105  	scope              apiextensionsv1.ResourceScope
   106  }
   107  
   108  // markObserved marks the fact that a node was observed as a concrete object.
   109  func (n *node) markObserved() {
   110  	n.virtual = false
   111  }
   112  
   113  func (n *node) addOwner(owner *node, attributes ownerReferenceAttributes) {
   114  	n.owners[owner] = attributes
   115  }
   116  
   117  func (n *node) addSoftOwner(owner *node) {
   118  	n.softOwners[owner] = struct{}{}
   119  }
   120  
   121  func (n *node) isOwnedBy(other *node) bool {
   122  	_, ok := n.owners[other]
   123  	return ok
   124  }
   125  
   126  func (n *node) isSoftOwnedBy(other *node) bool {
   127  	_, ok := n.softOwners[other]
   128  	return ok
   129  }
   130  
   131  func (n *node) getFilename() string {
   132  	return n.identity.Kind + "_" + n.identity.Namespace + "_" + n.identity.Name + ".yaml"
   133  }
   134  
   135  func (n *node) identityStr() string {
   136  	return fmt.Sprintf("Kind=%s, Name=%s, Namespace=%s", n.identity.Kind, n.identity.Name, n.identity.Namespace)
   137  }
   138  
   139  func (n *node) captureAdditionalInformation(obj *unstructured.Unstructured) error {
   140  	// If the node is a cluster check it see if it is uses a managed topology.
   141  	// In case, it uses a managed topology capture the name of the cluster class in use.
   142  	if n.identity.GroupVersionKind().GroupKind() == clusterv1.GroupVersion.WithKind("Cluster").GroupKind() {
   143  		cluster := &clusterv1.Cluster{}
   144  		if err := localScheme.Convert(obj, cluster, nil); err != nil {
   145  			return errors.Wrapf(err, "failed to convert object %s to Cluster", n.identityStr())
   146  		}
   147  		if cluster.Spec.Topology != nil {
   148  			if n.additionalInfo == nil {
   149  				n.additionalInfo = map[string]interface{}{}
   150  			}
   151  			n.additionalInfo[clusterTopologyNameKey] = cluster.Spec.Topology.Class
   152  		}
   153  	}
   154  
   155  	// If the node is a ClusterResourceSetBinding capture the name of the cluster it is referencing to.
   156  	if n.identity.GroupVersionKind().GroupKind() == addonsv1.GroupVersion.WithKind("ClusterResourceSetBinding").GroupKind() {
   157  		binding := &addonsv1.ClusterResourceSetBinding{}
   158  		if err := localScheme.Convert(obj, binding, nil); err != nil {
   159  			return errors.Wrapf(err, "failed to convert object %s to ClusterResourceSetBinding", n.identityStr())
   160  		}
   161  		if n.additionalInfo == nil {
   162  			n.additionalInfo = map[string]interface{}{}
   163  		}
   164  		n.additionalInfo[clusterResourceSetBindingClusterNameKey] = binding.Spec.ClusterName
   165  	}
   166  	return nil
   167  }
   168  
   169  // objectGraph manages the Kubernetes object graph that is generated during the discovery phase for the move operation.
   170  type objectGraph struct {
   171  	proxy             Proxy
   172  	providerInventory InventoryClient
   173  	uidToNode         map[types.UID]*node
   174  	types             map[string]*discoveryTypeInfo
   175  }
   176  
   177  func newObjectGraph(proxy Proxy, providerInventory InventoryClient) *objectGraph {
   178  	return &objectGraph{
   179  		proxy:             proxy,
   180  		providerInventory: providerInventory,
   181  		uidToNode:         map[types.UID]*node{},
   182  	}
   183  }
   184  
   185  // addObj adds a Kubernetes object to the object graph that is generated during the move discovery phase.
   186  // During add, OwnerReferences are processed in order to create the dependency graph.
   187  func (o *objectGraph) addObj(obj *unstructured.Unstructured) error {
   188  	// Adds the node to the Graph.
   189  	newNode, err := o.objToNode(obj)
   190  	if err != nil {
   191  		return errors.Wrapf(err, "failed to create node for object (Kind=%s, Name=%s)", obj.GetKind(), obj.GetName())
   192  	}
   193  
   194  	// Process OwnerReferences; if the owner object does not exists yet, create a virtual node as a placeholder for it.
   195  	o.processOwnerReferences(obj, newNode)
   196  	return nil
   197  }
   198  
   199  // addRestoredObj adds a Kubernetes object to the object graph from file that is generated during a fromDirectory
   200  // Populates the restoredObject field to be referenced during fromDirectory
   201  // During add, OwnerReferences are processed in order to create the dependency graph.
   202  func (o *objectGraph) addRestoredObj(obj *unstructured.Unstructured) error {
   203  	// Add object to graph
   204  	if _, err := o.objToNode(obj); err != nil {
   205  		return errors.Wrapf(err, "failed to create node for object (Kind=%s, Name=%s)", obj.GetKind(), obj.GetName())
   206  	}
   207  
   208  	// Check to ensure node has been added to graph
   209  	node, found := o.uidToNode[obj.GetUID()]
   210  	if !found {
   211  		return errors.Errorf("error adding obj %v with id %v to object graph", obj.GetName(), obj.GetUID())
   212  	}
   213  
   214  	// Copy the raw object yaml to be referenced when restoring object
   215  	node.restoreObject = obj.DeepCopy()
   216  
   217  	// Process OwnerReferences; if the owner object does not exists yet, create a virtual node as a placeholder for it.
   218  	o.processOwnerReferences(obj, node)
   219  
   220  	return nil
   221  }
   222  
   223  func (o *objectGraph) processOwnerReferences(obj *unstructured.Unstructured, node *node) {
   224  	for _, ownerReference := range obj.GetOwnerReferences() {
   225  		ownerNode, ok := o.uidToNode[ownerReference.UID]
   226  		if !ok {
   227  			ownerNode = o.ownerToVirtualNode(ownerReference)
   228  		}
   229  
   230  		node.addOwner(ownerNode, ownerReferenceAttributes{
   231  			Controller:         ownerReference.Controller,
   232  			BlockOwnerDeletion: ownerReference.BlockOwnerDeletion,
   233  		})
   234  	}
   235  }
   236  
   237  // ownerToVirtualNode creates a virtual node as a placeholder for the Kubernetes owner object received in input.
   238  // The virtual node will be eventually converted to an actual node when the node will be visited during discovery.
   239  func (o *objectGraph) ownerToVirtualNode(owner metav1.OwnerReference) *node {
   240  	ownerNode := &node{
   241  		identity: corev1.ObjectReference{
   242  			APIVersion: owner.APIVersion,
   243  			Kind:       owner.Kind,
   244  			Name:       owner.Name,
   245  			UID:        owner.UID,
   246  			// NOTE: deferring initialization of fields derived from object meta to when the node reference is actually processed.
   247  		},
   248  		owners:     make(map[*node]ownerReferenceAttributes),
   249  		softOwners: make(map[*node]empty),
   250  		tenant:     make(map[*node]empty),
   251  		virtual:    true,
   252  		// NOTE: deferring initialization of fields derived from object meta to when the node reference is actually processed.
   253  	}
   254  
   255  	o.uidToNode[ownerNode.identity.UID] = ownerNode
   256  	return ownerNode
   257  }
   258  
   259  // objToNode creates a node for the Kubernetes object received in input.
   260  // If the node corresponding to the Kubernetes object already exists as a virtual node detected when processing OwnerReferences,
   261  // the node is marked as Observed.
   262  func (o *objectGraph) objToNode(obj *unstructured.Unstructured) (*node, error) {
   263  	existingNode, found := o.uidToNode[obj.GetUID()]
   264  	if found {
   265  		existingNode.markObserved()
   266  
   267  		if err := o.objInfoToNode(obj, existingNode); err != nil {
   268  			return nil, errors.Wrapf(err, "failed to add object info to node for object %s", existingNode.identityStr())
   269  		}
   270  
   271  		return existingNode, nil
   272  	}
   273  
   274  	newNode := &node{
   275  		identity: corev1.ObjectReference{
   276  			APIVersion: obj.GetAPIVersion(),
   277  			Kind:       obj.GetKind(),
   278  			UID:        obj.GetUID(),
   279  			Name:       obj.GetName(),
   280  			Namespace:  obj.GetNamespace(),
   281  		},
   282  		owners:         make(map[*node]ownerReferenceAttributes),
   283  		softOwners:     make(map[*node]empty),
   284  		tenant:         make(map[*node]empty),
   285  		virtual:        false,
   286  		additionalInfo: make(map[string]interface{}),
   287  	}
   288  	if err := o.objInfoToNode(obj, newNode); err != nil {
   289  		return nil, errors.Wrapf(err, "failed to add object info to node for object %s", existingNode.identityStr())
   290  	}
   291  
   292  	o.uidToNode[newNode.identity.UID] = newNode
   293  	return newNode, nil
   294  }
   295  
   296  func (o *objectGraph) objInfoToNode(obj *unstructured.Unstructured, n *node) error {
   297  	o.objMetaToNode(obj, n)
   298  	if err := n.captureAdditionalInformation(obj); err != nil {
   299  		return errors.Wrapf(err, "failed to capture additional information of object %s", n.identityStr())
   300  	}
   301  	return nil
   302  }
   303  
   304  func (o *objectGraph) objMetaToNode(obj *unstructured.Unstructured, n *node) {
   305  	n.identity.Namespace = obj.GetNamespace()
   306  	if _, ok := obj.GetLabels()[clusterctlv1.ClusterctlMoveLabel]; ok {
   307  		n.forceMove = true
   308  	}
   309  	if _, ok := obj.GetLabels()[clusterctlv1.ClusterctlMoveHierarchyLabel]; ok {
   310  		n.forceMoveHierarchy = true
   311  	}
   312  
   313  	kindAPIStr := getKindAPIString(metav1.TypeMeta{Kind: obj.GetKind(), APIVersion: obj.GetAPIVersion()})
   314  	if discoveryType, ok := o.types[kindAPIStr]; ok {
   315  		if !n.forceMove && discoveryType.forceMove {
   316  			n.forceMove = true
   317  		}
   318  
   319  		if !n.forceMoveHierarchy && discoveryType.forceMoveHierarchy {
   320  			n.forceMoveHierarchy = true
   321  		}
   322  
   323  		if discoveryType.scope == apiextensionsv1.ClusterScoped {
   324  			n.isGlobal = true
   325  		}
   326  	}
   327  
   328  	_, n.blockingMove = obj.GetAnnotations()[clusterctlv1.BlockMoveAnnotation]
   329  }
   330  
   331  // getDiscoveryTypes returns the list of TypeMeta to be considered for the move discovery phase.
   332  // This list includes all the types defines by the CRDs installed by clusterctl and the ConfigMap/Secret core types.
   333  func (o *objectGraph) getDiscoveryTypes(ctx context.Context) error {
   334  	crdList := &apiextensionsv1.CustomResourceDefinitionList{}
   335  	getDiscoveryTypesBackoff := newReadBackoff()
   336  	if err := retryWithExponentialBackoff(ctx, getDiscoveryTypesBackoff, func(ctx context.Context) error {
   337  		return getCRDList(ctx, o.proxy, crdList)
   338  	}); err != nil {
   339  		return err
   340  	}
   341  
   342  	o.types = make(map[string]*discoveryTypeInfo)
   343  
   344  	for _, crd := range crdList.Items {
   345  		for _, version := range crd.Spec.Versions {
   346  			if !version.Storage {
   347  				continue
   348  			}
   349  
   350  			// If a CRD is labeled with force move-hierarchy, keep track of this so all the objects of this kind could be moved
   351  			// together with their descendants identified via the owner chain.
   352  			// NOTE: Cluster, ClusterClass and ClusterResourceSet are automatically considered as force move-hierarchy.
   353  			forceMoveHierarchy := false
   354  			if crd.Spec.Group == clusterv1.GroupVersion.Group && crd.Spec.Names.Kind == "Cluster" {
   355  				forceMoveHierarchy = true
   356  			}
   357  			if crd.Spec.Group == clusterv1.GroupVersion.Group && crd.Spec.Names.Kind == "ClusterClass" {
   358  				forceMoveHierarchy = true
   359  			}
   360  			if crd.Spec.Group == addonsv1.GroupVersion.Group && crd.Spec.Names.Kind == "ClusterResourceSet" {
   361  				forceMoveHierarchy = true
   362  			}
   363  			if _, ok := crd.Labels[clusterctlv1.ClusterctlMoveHierarchyLabel]; ok {
   364  				forceMoveHierarchy = true
   365  			}
   366  
   367  			// If a CRD is with as force move, keep track of this so all the objects of this type could be moved.
   368  			// NOTE: if a kind is set for force move-hierarchy, it is also automatically force moved.
   369  			forceMove := forceMoveHierarchy
   370  			if _, ok := crd.Labels[clusterctlv1.ClusterctlMoveLabel]; ok {
   371  				forceMove = true
   372  			}
   373  
   374  			typeMeta := metav1.TypeMeta{
   375  				Kind: crd.Spec.Names.Kind,
   376  				APIVersion: metav1.GroupVersion{
   377  					Group:   crd.Spec.Group,
   378  					Version: version.Name,
   379  				}.String(),
   380  			}
   381  
   382  			o.types[getKindAPIString(typeMeta)] = &discoveryTypeInfo{
   383  				typeMeta:           typeMeta,
   384  				forceMove:          forceMove,
   385  				forceMoveHierarchy: forceMoveHierarchy,
   386  				scope:              crd.Spec.Scope,
   387  			}
   388  		}
   389  	}
   390  
   391  	secretTypeMeta := metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"}
   392  	o.types[getKindAPIString(secretTypeMeta)] = &discoveryTypeInfo{typeMeta: secretTypeMeta}
   393  
   394  	configMapTypeMeta := metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"}
   395  	o.types[getKindAPIString(configMapTypeMeta)] = &discoveryTypeInfo{typeMeta: configMapTypeMeta}
   396  
   397  	return nil
   398  }
   399  
   400  // getKindAPIString returns a concatenated string of the API name and the plural of the kind
   401  // Ex: KIND=Foo API NAME=foo.bar.domain.tld => foos.foo.bar.domain.tld.
   402  func getKindAPIString(typeMeta metav1.TypeMeta) string {
   403  	api := strings.Split(typeMeta.APIVersion, "/")[0]
   404  	return fmt.Sprintf("%ss.%s", strings.ToLower(typeMeta.Kind), api)
   405  }
   406  
   407  func getCRDList(ctx context.Context, proxy Proxy, crdList *apiextensionsv1.CustomResourceDefinitionList) error {
   408  	c, err := proxy.NewClient(ctx)
   409  	if err != nil {
   410  		return err
   411  	}
   412  
   413  	if err := c.List(ctx, crdList, client.HasLabels{clusterctlv1.ClusterctlLabel}); err != nil {
   414  		return errors.Wrap(err, "failed to get the list of CRDs required for the move discovery phase")
   415  	}
   416  	return nil
   417  }
   418  
   419  // Discovery reads all the Kubernetes objects existing in a namespace (or in all namespaces if empty) for the types received in input, and then adds
   420  // everything to the objects graph.
   421  func (o *objectGraph) Discovery(ctx context.Context, namespace string) error {
   422  	log := logf.Log
   423  	log.Info("Discovering Cluster API objects")
   424  
   425  	selectors := []client.ListOption{}
   426  	if namespace != "" {
   427  		selectors = append(selectors, client.InNamespace(namespace))
   428  	}
   429  
   430  	discoveryBackoff := newReadBackoff()
   431  	for _, discoveryType := range o.types {
   432  		typeMeta := discoveryType.typeMeta
   433  		objList := new(unstructured.UnstructuredList)
   434  
   435  		if err := retryWithExponentialBackoff(ctx, discoveryBackoff, func(ctx context.Context) error {
   436  			return getObjList(ctx, o.proxy, typeMeta, selectors, objList)
   437  		}); err != nil {
   438  			return err
   439  		}
   440  
   441  		// if we are discovering Secrets, also secrets from the providers namespace should be included.
   442  		if discoveryType.typeMeta.GetObjectKind().GroupVersionKind().GroupKind() == corev1.SchemeGroupVersion.WithKind("Secret").GroupKind() {
   443  			providers, err := o.providerInventory.List(ctx)
   444  			if err != nil {
   445  				return err
   446  			}
   447  			for _, p := range providers.Items {
   448  				if p.Type == string(clusterctlv1.InfrastructureProviderType) {
   449  					providerNamespaceSelector := []client.ListOption{client.InNamespace(p.Namespace)}
   450  					providerNamespaceSecretList := new(unstructured.UnstructuredList)
   451  					if err := retryWithExponentialBackoff(ctx, discoveryBackoff, func(ctx context.Context) error {
   452  						return getObjList(ctx, o.proxy, typeMeta, providerNamespaceSelector, providerNamespaceSecretList)
   453  					}); err != nil {
   454  						return err
   455  					}
   456  					objList.Items = append(objList.Items, providerNamespaceSecretList.Items...)
   457  				}
   458  			}
   459  		}
   460  
   461  		if len(objList.Items) == 0 {
   462  			continue
   463  		}
   464  
   465  		log.V(5).Info(typeMeta.Kind, "Count", len(objList.Items))
   466  		for i := range objList.Items {
   467  			obj := objList.Items[i]
   468  			if err := o.addObj(&obj); err != nil {
   469  				return errors.Wrapf(err, "failed to add obj (Kind=%s, Name=%s) to graph", obj.GetKind(), obj.GetName())
   470  			}
   471  		}
   472  	}
   473  
   474  	log.V(1).Info("Total objects", "Count", len(o.uidToNode))
   475  
   476  	// Completes the graph by searching for soft ownership relations such as secrets linked to the cluster
   477  	// by a naming convention (without any explicit OwnerReference).
   478  	o.setSoftOwnership()
   479  
   480  	// Completes the graph by setting for each node the list of tenants the node belongs to.
   481  	o.setTenants()
   482  
   483  	return nil
   484  }
   485  
   486  func getObjList(ctx context.Context, proxy Proxy, typeMeta metav1.TypeMeta, selectors []client.ListOption, objList *unstructured.UnstructuredList) error {
   487  	c, err := proxy.NewClient(ctx)
   488  	if err != nil {
   489  		return err
   490  	}
   491  
   492  	objList.SetAPIVersion(typeMeta.APIVersion)
   493  	objList.SetKind(typeMeta.Kind)
   494  
   495  	if err := c.List(ctx, objList, selectors...); err != nil {
   496  		if apierrors.IsNotFound(err) {
   497  			return nil
   498  		}
   499  		return errors.Wrapf(err, "failed to list %q resources", objList.GroupVersionKind())
   500  	}
   501  	return nil
   502  }
   503  
   504  // getClusters returns the list of Clusters existing in the object graph.
   505  func (o *objectGraph) getClusters() []*node {
   506  	clusters := []*node{}
   507  	for _, node := range o.uidToNode {
   508  		if node.identity.GroupVersionKind().GroupKind() == clusterv1.GroupVersion.WithKind("Cluster").GroupKind() {
   509  			clusters = append(clusters, node)
   510  		}
   511  	}
   512  	return clusters
   513  }
   514  
   515  // getClusterClasses returns the list of ClusterClasses existing in the object graph.
   516  func (o *objectGraph) getClusterClasses() []*node {
   517  	clusterClasses := []*node{}
   518  	for _, node := range o.uidToNode {
   519  		if node.identity.GroupVersionKind().GroupKind() == clusterv1.GroupVersion.WithKind("ClusterClass").GroupKind() {
   520  			clusterClasses = append(clusterClasses, node)
   521  		}
   522  	}
   523  	return clusterClasses
   524  }
   525  
   526  // getClusterResourceSetBinding returns the list of ClusterResourceSetBinding existing in the object graph.
   527  func (o *objectGraph) getClusterResourceSetBinding() []*node {
   528  	crs := []*node{}
   529  	for _, node := range o.uidToNode {
   530  		if node.identity.GroupVersionKind().GroupKind() == addonsv1.GroupVersion.WithKind("ClusterResourceSetBinding").GroupKind() {
   531  			crs = append(crs, node)
   532  		}
   533  	}
   534  	return crs
   535  }
   536  
   537  // getClusters returns the list of Secrets existing in the object graph.
   538  func (o *objectGraph) getSecrets() []*node {
   539  	secrets := []*node{}
   540  	for _, node := range o.uidToNode {
   541  		if node.identity.APIVersion == "v1" && node.identity.Kind == "Secret" {
   542  			secrets = append(secrets, node)
   543  		}
   544  	}
   545  	return secrets
   546  }
   547  
   548  // getNodes returns the list of nodes existing in the object graph.
   549  func (o *objectGraph) getNodes() []*node {
   550  	nodes := []*node{}
   551  	for _, node := range o.uidToNode {
   552  		nodes = append(nodes, node)
   553  	}
   554  	return nodes
   555  }
   556  
   557  // getCRSs returns the list of ClusterResourceSet existing in the object graph.
   558  func (o *objectGraph) getCRSs() []*node {
   559  	clusters := []*node{}
   560  	for _, node := range o.uidToNode {
   561  		if node.identity.GroupVersionKind().GroupKind() == addonsv1.GroupVersion.WithKind("ClusterResourceSet").GroupKind() {
   562  			clusters = append(clusters, node)
   563  		}
   564  	}
   565  	return clusters
   566  }
   567  
   568  // getMoveNodes returns the list of nodes existing in the object graph that belong at least to one tenant (e.g Cluster or to a ClusterResourceSet)
   569  // or it is labeled for force move (at object level or at CRD level).
   570  func (o *objectGraph) getMoveNodes() []*node {
   571  	nodes := []*node{}
   572  	for _, node := range o.uidToNode {
   573  		if len(node.tenant) > 0 || node.forceMove {
   574  			nodes = append(nodes, node)
   575  		}
   576  	}
   577  	return nodes
   578  }
   579  
   580  // getMachines returns the list of Machine existing in the object graph.
   581  func (o *objectGraph) getMachines() []*node {
   582  	machines := []*node{}
   583  	for _, node := range o.uidToNode {
   584  		if node.identity.GroupVersionKind().GroupKind() == clusterv1.GroupVersion.WithKind("Machine").GroupKind() {
   585  			machines = append(machines, node)
   586  		}
   587  	}
   588  	return machines
   589  }
   590  
   591  // setSoftOwnership searches for soft ownership relations such as secrets linked to the cluster by a naming convention (without any explicit OwnerReference).
   592  func (o *objectGraph) setSoftOwnership() {
   593  	log := logf.Log
   594  	clusters := o.getClusters()
   595  	for _, secret := range o.getSecrets() {
   596  		// If the secret has at least one OwnerReference ignore it.
   597  		// NB. Cluster API generated secrets have an explicit OwnerReference to the ControlPlane or the KubeadmConfig object while user provided secrets might not have one.
   598  		if len(secret.owners) > 0 {
   599  			continue
   600  		}
   601  
   602  		// If the secret name is not a valid cluster secret name, ignore it.
   603  		secretClusterName, _, err := secretutil.ParseSecretName(secret.identity.Name)
   604  		if err != nil {
   605  			log.V(5).Info("Excluding secret from move (not linked with any Cluster)", "name", secret.identity.Name)
   606  			continue
   607  		}
   608  
   609  		// If the secret is linked to a cluster, then add the cluster to the list of the secrets's softOwners.
   610  		for _, cluster := range clusters {
   611  			if secretClusterName == cluster.identity.Name && secret.identity.Namespace == cluster.identity.Namespace {
   612  				secret.addSoftOwner(cluster)
   613  			}
   614  		}
   615  	}
   616  
   617  	clusterClasses := o.getClusterClasses()
   618  	// Cluster that uses a ClusterClass are soft owned by that ClusterClass.
   619  	for _, clusterClass := range clusterClasses {
   620  		for _, cluster := range clusters {
   621  			// if the cluster uses a managed topology and uses the clusterclass
   622  			// set the clusterclass as a soft owner of the cluster.
   623  			if className, ok := cluster.additionalInfo[clusterTopologyNameKey]; ok {
   624  				if className == clusterClass.identity.Name && clusterClass.identity.Namespace == cluster.identity.Namespace {
   625  					cluster.addSoftOwner(clusterClass)
   626  				}
   627  			}
   628  		}
   629  	}
   630  
   631  	crsBindings := o.getClusterResourceSetBinding()
   632  	// ClusterResourceSetBinding that refers to a Cluster are soft owned by that Cluster.
   633  	for _, binding := range crsBindings {
   634  		clusterName, ok := binding.additionalInfo[clusterResourceSetBindingClusterNameKey]
   635  		if !ok {
   636  			continue
   637  		}
   638  
   639  		for _, cluster := range clusters {
   640  			if clusterName == cluster.identity.Name && binding.identity.Namespace == cluster.identity.Namespace {
   641  				binding.addSoftOwner(cluster)
   642  			}
   643  		}
   644  	}
   645  }
   646  
   647  // setTenants identifies all the nodes linked to a parent with forceMoveHierarchy = true (e.g. Clusters or ClusterResourceSet)
   648  // via the owner ref chain.
   649  func (o *objectGraph) setTenants() {
   650  	for _, node := range o.getNodes() {
   651  		if node.forceMoveHierarchy {
   652  			o.setTenant(node, node, node.isGlobal)
   653  		}
   654  	}
   655  }
   656  
   657  // setTenant sets a tenant for a node and for its own dependents/sofDependents.
   658  func (o *objectGraph) setTenant(node, tenant *node, isGlobalHierarchy bool) {
   659  	node.tenant[tenant] = empty{}
   660  	node.isGlobalHierarchy = node.isGlobalHierarchy || isGlobalHierarchy
   661  	for _, other := range o.getNodes() {
   662  		if other.isOwnedBy(node) || other.isSoftOwnedBy(node) {
   663  			o.setTenant(other, tenant, isGlobalHierarchy)
   664  		}
   665  	}
   666  }
   667  
   668  // checkVirtualNode logs if nodes are still virtual.
   669  func (o *objectGraph) checkVirtualNode() {
   670  	log := logf.Log
   671  	for _, node := range o.uidToNode {
   672  		if node.virtual {
   673  			log.V(5).Info("Object won't be moved because it's not included in GVK considered for move", "kind", node.identity.Kind, "name", node.identity.Name)
   674  		}
   675  	}
   676  }