@@ -17,6 +17,7 @@ limitations under the License.
1717package cluster
1818
1919import (
20+ "cmp"
2021 "context"
2122 "fmt"
2223 "strings"
@@ -28,16 +29,22 @@ import (
2829 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2930 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3031 "k8s.io/apimachinery/pkg/types"
32+ kerrors "k8s.io/apimachinery/pkg/util/errors"
33+ "k8s.io/apimachinery/pkg/util/sets"
34+ "k8s.io/apimachinery/pkg/util/wait"
35+ "k8s.io/klog/v2"
3136 "sigs.k8s.io/controller-runtime/pkg/client"
3237
3338 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3439 clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
3540 logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
41+ "sigs.k8s.io/cluster-api/controllers/external"
3642 addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1"
3743 secretutil "sigs.k8s.io/cluster-api/util/secret"
3844)
3945
4046const clusterTopologyNameKey = "cluster.spec.topology.class"
47+ const clusterTopologyNamespaceKey = "cluster.spec.topology.classNamespace"
4148const clusterResourceSetBindingClusterNameKey = "clusterresourcesetbinding.spec.clustername"
4249
4350type empty struct {}
@@ -74,6 +81,9 @@ type node struct {
7481 // When this flag is true the object should not be deleted from the source cluster.
7582 isGlobalHierarchy bool
7683
84+ // shouldNotDelete marks object and direct its descendants to not be deleted
85+ shouldNotDelete bool
86+
7787 // virtual records if this node was discovered indirectly, e.g. by processing an OwnerRef, but not yet observed as a concrete object.
7888 virtual bool
7989
@@ -149,6 +159,7 @@ func (n *node) captureAdditionalInformation(obj *unstructured.Unstructured) erro
149159 n .additionalInfo = map [string ]interface {}{}
150160 }
151161 n .additionalInfo [clusterTopologyNameKey ] = cluster .GetClassKey ().Name
162+ n .additionalInfo [clusterTopologyNamespaceKey ] = cluster .GetClassKey ().Namespace
152163 }
153164 }
154165
@@ -433,7 +444,7 @@ func (o *objectGraph) Discovery(ctx context.Context, namespace string) error {
433444 objList := new (unstructured.UnstructuredList )
434445
435446 if err := retryWithExponentialBackoff (ctx , discoveryBackoff , func (ctx context.Context ) error {
436- return getObjList (ctx , o .proxy , typeMeta , selectors , objList )
447+ return getObjList (ctx , o .proxy , & typeMeta , selectors , objList )
437448 }); err != nil {
438449 return err
439450 }
@@ -449,7 +460,7 @@ func (o *objectGraph) Discovery(ctx context.Context, namespace string) error {
449460 providerNamespaceSelector := []client.ListOption {client .InNamespace (p .Namespace )}
450461 providerNamespaceSecretList := new (unstructured.UnstructuredList )
451462 if err := retryWithExponentialBackoff (ctx , discoveryBackoff , func (ctx context.Context ) error {
452- return getObjList (ctx , o .proxy , typeMeta , providerNamespaceSelector , providerNamespaceSecretList )
463+ return getObjList (ctx , o .proxy , & typeMeta , providerNamespaceSelector , providerNamespaceSecretList )
453464 }); err != nil {
454465 return err
455466 }
@@ -471,6 +482,76 @@ func (o *objectGraph) Discovery(ctx context.Context, namespace string) error {
471482 }
472483 }
473484
485+ // On top of the object in the namespace being moved (secrets from the providers namespace),
486+ // it is also required to discover ClusterClasses in other namespaces referenced by clusters in the namespace being moved.
487+ discoveredExternalCC := sets.Set [string ]{}
488+ for _ , cluster := range o .getClusters () {
489+ className , hasName := cluster .additionalInfo [clusterTopologyNameKey ]
490+ classNamespace , hasNamespace := cluster .additionalInfo [clusterTopologyNamespaceKey ]
491+ // If the cluster doesn't have a reference to a ClusterClass, no-op.
492+ if ! hasName || ! hasNamespace {
493+ continue
494+ }
495+
496+ // If the cluster reference a ClusterClass in the namespace being moved, no-op (it has been already discovered).
497+ if classNamespace == namespace {
498+ continue
499+ }
500+
501+ // If the referenced ClusterClass has been already discovered, no-op.
502+ externalCCKey := klog .KRef (classNamespace .(string ), className .(string ))
503+ if discoveredExternalCC .Has (externalCCKey .String ()) {
504+ continue
505+ }
506+
507+ // Get the CC and referenced templates.
508+ ccUnstructured , err := o .fetchRef (ctx , discoveryBackoff , & corev1.ObjectReference {
509+ Kind : "ClusterClass" ,
510+ Namespace : externalCCKey .Namespace ,
511+ Name : externalCCKey .Name ,
512+ APIVersion : clusterv1 .GroupVersion .String (),
513+ })
514+ if err != nil {
515+ return errors .Wrap (err , "failed to get ClusterClass" )
516+ }
517+
518+ cc := & clusterv1.ClusterClass {}
519+ if err := localScheme .Convert (ccUnstructured , cc , nil ); err != nil {
520+ return errors .Wrap (err , "failed to convert Unstructured to ClusterClass" )
521+ }
522+
523+ errs := []error {}
524+ _ , err = o .fetchRef (ctx , discoveryBackoff , cc .Spec .Infrastructure .Ref )
525+ errs = append (errs , err )
526+ _ , err = o .fetchRef (ctx , discoveryBackoff , cc .Spec .ControlPlane .Ref )
527+ errs = append (errs , err )
528+
529+ if cc .Spec .ControlPlane .MachineInfrastructure != nil {
530+ _ , err = o .fetchRef (ctx , discoveryBackoff , cc .Spec .ControlPlane .MachineInfrastructure .Ref )
531+ errs = append (errs , err )
532+ }
533+
534+ for _ , mdClass := range cc .Spec .Workers .MachineDeployments {
535+ _ , err = o .fetchRef (ctx , discoveryBackoff , mdClass .Template .Infrastructure .Ref )
536+ errs = append (errs , err )
537+ _ , err = o .fetchRef (ctx , discoveryBackoff , mdClass .Template .Bootstrap .Ref )
538+ errs = append (errs , err )
539+ }
540+
541+ for _ , mpClass := range cc .Spec .Workers .MachinePools {
542+ _ , err = o .fetchRef (ctx , discoveryBackoff , mpClass .Template .Infrastructure .Ref )
543+ errs = append (errs , err )
544+ _ , err = o .fetchRef (ctx , discoveryBackoff , mpClass .Template .Bootstrap .Ref )
545+ errs = append (errs , err )
546+ }
547+
548+ if err := kerrors .NewAggregate (errs ); err != nil {
549+ return errors .Wrap (err , "failed to fetch ClusterClass references" )
550+ }
551+
552+ discoveredExternalCC .Insert (externalCCKey .String ())
553+ }
554+
474555 log .V (1 ).Info ("Total objects" , "count" , len (o .uidToNode ))
475556
476557 // Completes the graph by searching for soft ownership relations such as secrets linked to the cluster
@@ -480,23 +561,52 @@ func (o *objectGraph) Discovery(ctx context.Context, namespace string) error {
480561 // Completes the graph by setting for each node the list of tenants the node belongs to.
481562 o .setTenants ()
482563
483- return nil
564+ // Ensure objects which are referenced across namespaces are not deleted.
565+ return o .setShouldNotDelete (ctx , namespace )
484566}
485567
486- func getObjList (ctx context.Context , proxy Proxy , typeMeta metav1.TypeMeta , selectors []client.ListOption , objList * unstructured.UnstructuredList ) error {
568+ // fetchRef collects specified reference and adds to moved objects.
569+ func (o * objectGraph ) fetchRef (ctx context.Context , opts wait.Backoff , ref * corev1.ObjectReference ) (* unstructured.Unstructured , error ) {
570+ if ref == nil {
571+ return nil , nil
572+ }
573+
574+ var obj * unstructured.Unstructured
575+
576+ if err := retryWithExponentialBackoff (ctx , opts , func (ctx context.Context ) error {
577+ c , err := o .proxy .NewClient (ctx )
578+ if err != nil {
579+ return err
580+ }
581+
582+ obj , err = external .Get (ctx , c , ref )
583+ return err
584+ }); err != nil {
585+ return nil , errors .Wrapf (err , "failed to get referenced %q resource" , ref .String ())
586+ }
587+
588+ if err := o .addObj (obj ); err != nil {
589+ return nil , errors .Wrapf (err , "failed to add obj (Kind=%s, Name=%s) to graph" , obj .GetKind (), obj .GetName ())
590+ }
591+
592+ return obj , nil
593+ }
594+
595+ func getObjList (ctx context.Context , proxy Proxy , typeMeta * metav1.TypeMeta , selectors []client.ListOption , objList client.ObjectList ) error {
487596 c , err := proxy .NewClient (ctx )
488597 if err != nil {
489598 return err
490599 }
491600
492- objList .SetAPIVersion (typeMeta .APIVersion )
493- objList .SetKind (typeMeta .Kind )
601+ if typeMeta != nil {
602+ objList .GetObjectKind ().SetGroupVersionKind (typeMeta .GroupVersionKind ())
603+ }
494604
495605 if err := c .List (ctx , objList , selectors ... ); err != nil {
496606 if apierrors .IsNotFound (err ) {
497607 return nil
498608 }
499- return errors .Wrapf (err , "failed to list %q resources" , objList .GroupVersionKind ())
609+ return errors .Wrapf (err , "failed to list %q resources" , objList .GetObjectKind (). GroupVersionKind ())
500610 }
501611 return nil
502612}
@@ -615,15 +725,20 @@ func (o *objectGraph) setSoftOwnership() {
615725 }
616726
617727 clusterClasses := o .getClusterClasses ()
728+
618729 // Cluster that uses a ClusterClass are soft owned by that ClusterClass.
619730 for _ , clusterClass := range clusterClasses {
620731 for _ , cluster := range clusters {
621732 // if the cluster uses a managed topology and uses the clusterclass
622733 // 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- }
734+ className , hasName := cluster .additionalInfo [clusterTopologyNameKey ]
735+ classNamespace , hasNamespace := cluster .additionalInfo [clusterTopologyNamespaceKey ]
736+ if namespace , ok := classNamespace .(string ); ! hasNamespace || ! ok {
737+ classNamespace = cmp .Or (namespace , cluster .identity .Namespace )
738+ }
739+
740+ if hasName && className == clusterClass .identity .Name && clusterClass .identity .Namespace == classNamespace {
741+ cluster .addSoftOwner (clusterClass )
627742 }
628743 }
629744 }
@@ -649,18 +764,18 @@ func (o *objectGraph) setSoftOwnership() {
649764func (o * objectGraph ) setTenants () {
650765 for _ , node := range o .getNodes () {
651766 if node .forceMoveHierarchy {
652- o .setTenant (node , node , node .isGlobal )
767+ o .setTenantHierarchy (node , node , node .isGlobal )
653768 }
654769 }
655770}
656771
657- // setTenant sets a tenant for a node and for its own dependents/sofDependents .
658- func (o * objectGraph ) setTenant (node , tenant * node , isGlobalHierarchy bool ) {
772+ // setTenantHierarchy sets a tenant for a node and for its own dependents/softDependents .
773+ func (o * objectGraph ) setTenantHierarchy (node , tenant * node , isGlobalHierarchy bool ) {
659774 node .tenant [tenant ] = empty {}
660775 node .isGlobalHierarchy = node .isGlobalHierarchy || isGlobalHierarchy
661776 for _ , other := range o .getNodes () {
662777 if other .isOwnedBy (node ) || other .isSoftOwnedBy (node ) {
663- o .setTenant (other , tenant , isGlobalHierarchy )
778+ o .setTenantHierarchy (other , tenant , isGlobalHierarchy )
664779 }
665780 }
666781}
@@ -674,3 +789,68 @@ func (o *objectGraph) checkVirtualNode() {
674789 }
675790 }
676791}
792+
793+ // Ensure objects which are referenced across namespaces are not deleted.
794+ func (o * objectGraph ) setShouldNotDelete (ctx context.Context , namespace string ) error {
795+ if namespace == "" {
796+ return nil
797+ }
798+
799+ // If there are ClusterClasses outside of the namespace being moved, those CC should not be deleted (we are moving a namespace).
800+ for _ , class := range o .getClusterClasses () {
801+ if class .identity .Namespace != namespace {
802+ class .shouldNotDelete = true
803+ // Ensure that also the templates referenced by the CC won't be deleted.
804+ o .setShouldNotDeleteHierarchy (class )
805+ }
806+ }
807+
808+ // If there are clusters outside of the namespace being moved and referencing one of ClusterClass in the namespace being moved,
809+ // ensure those ClusterClass are not deleted.
810+ discoveryBackoff := newReadBackoff ()
811+ allClusters := & clusterv1.ClusterList {}
812+ if err := retryWithExponentialBackoff (ctx , discoveryBackoff , func (ctx context.Context ) error {
813+ return getObjList (ctx , o .proxy , nil , []client.ListOption {}, allClusters )
814+ }); err != nil {
815+ return err
816+ }
817+
818+ for _ , cluster := range allClusters .Items {
819+ // ignore cluster in the namespace being moved.
820+ if cluster .Namespace == namespace {
821+ continue
822+ }
823+
824+ // ignore cluster not using a CC.
825+ if cluster .Spec .Topology == nil {
826+ continue
827+ }
828+
829+ // ignore cluster not referencing a CC in the namespace being moved.
830+ if cluster .Spec .Topology .ClassNamespace != namespace {
831+ continue
832+ }
833+
834+ // Otherwise mark the referenced CC as should not be deleted.
835+ for _ , class := range o .getClusterClasses () {
836+ if class .identity .Namespace == cluster .Spec .Topology .ClassNamespace && class .identity .Name == cluster .Spec .Topology .Class {
837+ class .shouldNotDelete = true
838+ // Ensure that also the templates referenced by the CC won't be deleted.
839+ o .setShouldNotDeleteHierarchy (class )
840+ }
841+ }
842+ }
843+
844+ return nil
845+ }
846+
847+ // setShouldNotDeleteHierarchy sets should not delete for a node and for its own dependents/softDependents.
848+ func (o * objectGraph ) setShouldNotDeleteHierarchy (node * node ) {
849+ node .shouldNotDelete = true
850+ for _ , other := range o .getNodes () {
851+ // Skip removal only for direct owners from the object namespace
852+ if other .isOwnedBy (node ) {
853+ o .setShouldNotDeleteHierarchy (other )
854+ }
855+ }
856+ }
0 commit comments