diff --git a/cmd/clusterctl/pkg/client/cluster/mover.go b/cmd/clusterctl/pkg/client/cluster/mover.go index 255ec19b2f35..800904e6105b 100644 --- a/cmd/clusterctl/pkg/client/cluster/mover.go +++ b/cmd/clusterctl/pkg/client/cluster/mover.go @@ -22,11 +22,13 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" kerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" "sigs.k8s.io/controller-runtime/pkg/client" @@ -94,6 +96,11 @@ func (o *objectMover) move(graph *objectGraph, toProxy Proxy) error { return err } + // Ensure all the expected target namespaces are in place before creating objects. + if err := o.ensureNamespaces(graph, toProxy); err != nil { + return err + } + // Define the move sequence by processing the ownerReference chain, so we ensure that a Kubernetes object is moved only after its owners. // The sequence is bases on object graph nodes, each one representing a Kubernetes object; nodes are grouped, so bulk of nodes can be moved in parallel. e.g. // - All the Clusters should be moved first (group 1, processed in parallel) @@ -229,6 +236,78 @@ func setClusterPause(proxy Proxy, clusters []*node, value bool, log logr.Logger) return nil } +// ensureNamespaces ensures all the expected target namespaces are in place before creating objects. +func (o *objectMover) ensureNamespaces(graph *objectGraph, toProxy Proxy) error { + cs, err := toProxy.NewClient() + if err != nil { + return err + } + + namespaces := sets.NewString() + for _, node := range graph.getNodesWithClusterTenants() { + namespace := node.identity.Namespace + + // If the namespace was already processed, skip it. + if namespaces.Has(namespace) { + continue + } + namespaces.Insert(namespace) + + // Otherwise check if namespace exists (also dealing with RBAC restrictions). + ns := &corev1.Namespace{} + key := client.ObjectKey{ + Name: namespace, + } + + if err := cs.Get(ctx, key, ns); err == nil { + return nil + } + if apierrors.IsForbidden(err) { + namespaces := &corev1.NamespaceList{} + namespaceExists := false + for { + if err := cs.List(ctx, namespaces, client.Continue(namespaces.Continue)); err != nil { + return err + } + + for _, ns := range namespaces.Items { + if ns.Name == namespace { + namespaceExists = true + break + } + } + + if namespaces.Continue == "" { + break + } + } + if namespaceExists { + continue + } + } + if !apierrors.IsNotFound(err) { + return err + } + + // If the namespace does not exists, create it. + ns = &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Namespace", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + o.log.V(1).Info("Creating", ns.Kind, ns.Name) + if err := cs.Create(ctx, ns); err != nil && !apierrors.IsAlreadyExists(err) { + return err + } + } + + return nil +} + const ( retryCreateTargetObject = 3 retryIntervalCreateTargetObject = 1 * time.Second