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 }