Skip to content

Commit 55fd1f3

Browse files
committed
✨ feat: accept resource mutators in Move operation
Signed-off-by: Tarun Gupta Akirala <[email protected]>
1 parent aef77c3 commit 55fd1f3

File tree

6 files changed

+138
-42
lines changed

6 files changed

+138
-42
lines changed

api/v1beta1/cluster_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ const (
3434
// ClusterFinalizer is the finalizer used by the cluster controller to
3535
// cleanup the cluster resources when a Cluster is being deleted.
3636
ClusterFinalizer = "cluster.cluster.x-k8s.io"
37+
38+
// KindCluster represents the Kind of Cluster.
39+
KindCluster = "Cluster"
3740
)
3841

3942
// ANCHOR: ClusterSpec

api/v1beta1/clusterclass_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import (
2525
"k8s.io/apimachinery/pkg/util/intstr"
2626
)
2727

28+
// KindClusterClass represents the Kind of ClusterClass.
29+
const KindClusterClass = "ClusterClass"
30+
2831
// +kubebuilder:object:root=true
2932
// +kubebuilder:resource:path=clusterclasses,shortName=cc,scope=Namespaced,categories=cluster-api
3033
// +kubebuilder:storageversion

cmd/clusterctl/client/cluster/mover.go

Lines changed: 59 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,13 @@ import (
4242
"sigs.k8s.io/cluster-api/util/yaml"
4343
)
4444

45+
// ResourceMutatorFunc holds the type for mutators to be applied on resources during a move operation.
46+
type ResourceMutatorFunc func(u *unstructured.Unstructured)
47+
4548
// ObjectMover defines methods for moving Cluster API objects to another management cluster.
4649
type ObjectMover interface {
4750
// Move moves all the Cluster API objects existing in a namespace (or from all the namespaces if empty) to a target management cluster.
48-
Move(namespace string, toCluster Client, dryRun bool) error
51+
Move(namespace string, mutators []ResourceMutatorFunc, toCluster Client, dryRun bool) error
4952

5053
// ToDirectory writes all the Cluster API objects existing in a namespace (or from all the namespaces if empty) to a target directory.
5154
ToDirectory(namespace string, directory string) error
@@ -64,7 +67,7 @@ type objectMover struct {
6467
// ensure objectMover implements the ObjectMover interface.
6568
var _ ObjectMover = &objectMover{}
6669

67-
func (o *objectMover) Move(namespace string, toCluster Client, dryRun bool) error {
70+
func (o *objectMover) Move(namespace string, mutators []ResourceMutatorFunc, toCluster Client, dryRun bool) error {
6871
log := logf.Log
6972
log.Info("Performing move...")
7073
o.dryRun = dryRun
@@ -92,7 +95,7 @@ func (o *objectMover) Move(namespace string, toCluster Client, dryRun bool) erro
9295
proxy = toCluster.Proxy()
9396
}
9497

95-
return o.move(objectGraph, proxy)
98+
return o.move(objectGraph, proxy, mutators)
9699
}
97100

98101
func (o *objectMover) ToDirectory(namespace string, directory string) error {
@@ -309,7 +312,7 @@ func getMachineObj(proxy Proxy, machine *node, machineObj *clusterv1.Machine) er
309312
}
310313

311314
// Move moves all the Cluster API objects existing in a namespace (or from all the namespaces if empty) to a target management cluster.
312-
func (o *objectMover) move(graph *objectGraph, toProxy Proxy) error {
315+
func (o *objectMover) move(graph *objectGraph, toProxy Proxy, mutators []ResourceMutatorFunc) error {
313316
log := logf.Log
314317

315318
clusters := graph.getClusters()
@@ -320,12 +323,12 @@ func (o *objectMover) move(graph *objectGraph, toProxy Proxy) error {
320323

321324
// Sets the pause field on the Cluster object in the source management cluster, so the controllers stop reconciling it.
322325
log.V(1).Info("Pausing the source cluster")
323-
if err := setClusterPause(o.fromProxy, clusters, true, o.dryRun); err != nil {
326+
if err := setClusterPause(o.fromProxy, clusters, nil, true, o.dryRun); err != nil {
324327
return err
325328
}
326329

327330
log.V(1).Info("Pausing the source ClusterClasses")
328-
if err := setClusterClassPause(o.fromProxy, clusterClasses, true, o.dryRun); err != nil {
331+
if err := setClusterClassPause(o.fromProxy, clusterClasses, nil, true, o.dryRun); err != nil {
329332
return errors.Wrap(err, "error pausing ClusterClasses")
330333
}
331334

@@ -345,7 +348,7 @@ func (o *objectMover) move(graph *objectGraph, toProxy Proxy) error {
345348
// Create all objects group by group, ensuring all the ownerReferences are re-created.
346349
log.Info("Creating objects in the target cluster")
347350
for groupIndex := 0; groupIndex < len(moveSequence.groups); groupIndex++ {
348-
if err := o.createGroup(moveSequence.getGroup(groupIndex), toProxy); err != nil {
351+
if err := o.createGroup(moveSequence.getGroup(groupIndex), toProxy, mutators); err != nil {
349352
return err
350353
}
351354
}
@@ -360,13 +363,13 @@ func (o *objectMover) move(graph *objectGraph, toProxy Proxy) error {
360363

361364
// Resume the ClusterClasses in the target management cluster, so the controllers start reconciling it.
362365
log.V(1).Info("Resuming the target ClusterClasses")
363-
if err := setClusterClassPause(toProxy, clusterClasses, false, o.dryRun); err != nil {
366+
if err := setClusterClassPause(toProxy, clusterClasses, mutators, false, o.dryRun); err != nil {
364367
return errors.Wrap(err, "error resuming ClusterClasses")
365368
}
366369

367370
// Reset the pause field on the Cluster object in the target management cluster, so the controllers start reconciling it.
368371
log.V(1).Info("Resuming the target cluster")
369-
return setClusterPause(toProxy, clusters, false, o.dryRun)
372+
return setClusterPause(toProxy, clusters, mutators, false, o.dryRun)
370373
}
371374

372375
func (o *objectMover) toDirectory(graph *objectGraph, directory string) error {
@@ -380,12 +383,12 @@ func (o *objectMover) toDirectory(graph *objectGraph, directory string) error {
380383

381384
// Sets the pause field on the Cluster object in the source management cluster, so the controllers stop reconciling it.
382385
log.V(1).Info("Pausing the source cluster")
383-
if err := setClusterPause(o.fromProxy, clusters, true, o.dryRun); err != nil {
386+
if err := setClusterPause(o.fromProxy, clusters, nil, true, o.dryRun); err != nil {
384387
return err
385388
}
386389

387390
log.V(1).Info("Pausing the source ClusterClasses")
388-
if err := setClusterClassPause(o.fromProxy, clusterClasses, true, o.dryRun); err != nil {
391+
if err := setClusterClassPause(o.fromProxy, clusterClasses, nil, true, o.dryRun); err != nil {
389392
return errors.Wrap(err, "error pausing ClusterClasses")
390393
}
391394

@@ -406,13 +409,13 @@ func (o *objectMover) toDirectory(graph *objectGraph, directory string) error {
406409

407410
// Resume the ClusterClasses in the target management cluster, so the controllers start reconciling it.
408411
log.V(1).Info("Resuming the target ClusterClasses")
409-
if err := setClusterClassPause(o.fromProxy, clusterClasses, false, o.dryRun); err != nil {
412+
if err := setClusterClassPause(o.fromProxy, clusterClasses, nil, false, o.dryRun); err != nil {
410413
return errors.Wrap(err, "error resuming ClusterClasses")
411414
}
412415

413416
// Reset the pause field on the Cluster object in the target management cluster, so the controllers start reconciling it.
414417
log.V(1).Info("Resuming the source cluster")
415-
return setClusterPause(o.fromProxy, clusters, false, o.dryRun)
418+
return setClusterPause(o.fromProxy, clusters, nil, false, o.dryRun)
416419
}
417420

418421
func (o *objectMover) fromDirectory(graph *objectGraph, toProxy Proxy) error {
@@ -447,14 +450,14 @@ func (o *objectMover) fromDirectory(graph *objectGraph, toProxy Proxy) error {
447450
// Resume reconciling the ClusterClasses after being restored from a backup.
448451
// By default, during backup, ClusterClasses are paused so they must be unpaused to be used again
449452
log.V(1).Info("Resuming the target ClusterClasses")
450-
if err := setClusterClassPause(toProxy, clusterClasses, false, o.dryRun); err != nil {
453+
if err := setClusterClassPause(toProxy, clusterClasses, nil, false, o.dryRun); err != nil {
451454
return errors.Wrap(err, "error resuming ClusterClasses")
452455
}
453456

454457
// Resume reconciling the Clusters after being restored from a directory.
455458
// By default, when moved to a directory, Clusters are paused, so they must be unpaused to be used again.
456459
log.V(1).Info("Resuming the target cluster")
457-
return setClusterPause(toProxy, clusters, false, o.dryRun)
460+
return setClusterPause(toProxy, clusters, nil, false, o.dryRun)
458461
}
459462

460463
// moveSequence defines a list of group of moveGroups.
@@ -533,7 +536,7 @@ func getMoveSequence(graph *objectGraph) *moveSequence {
533536
}
534537

535538
// setClusterPause sets the paused field on nodes referring to Cluster objects.
536-
func setClusterPause(proxy Proxy, clusters []*node, value bool, dryRun bool) error {
539+
func setClusterPause(proxy Proxy, clusters []*node, mutators []ResourceMutatorFunc, value bool, dryRun bool) error {
537540
if dryRun {
538541
return nil
539542
}
@@ -554,7 +557,7 @@ func setClusterPause(proxy Proxy, clusters []*node, value bool, dryRun bool) err
554557

555558
// Nb. The operation is wrapped in a retry loop to make setClusterPause more resilient to unexpected conditions.
556559
if err := retryWithExponentialBackoff(setClusterPauseBackoff, func() error {
557-
return patchCluster(proxy, cluster, patch)
560+
return patchCluster(proxy, cluster, patch, mutators)
558561
}); err != nil {
559562
return errors.Wrapf(err, "error setting Cluster.Spec.Paused=%t", value)
560563
}
@@ -563,7 +566,7 @@ func setClusterPause(proxy Proxy, clusters []*node, value bool, dryRun bool) err
563566
}
564567

565568
// setClusterClassPause sets the paused annotation on nodes referring to ClusterClass objects.
566-
func setClusterClassPause(proxy Proxy, clusterclasses []*node, pause bool, dryRun bool) error {
569+
func setClusterClassPause(proxy Proxy, clusterclasses []*node, mutators []ResourceMutatorFunc, pause bool, dryRun bool) error {
567570
if dryRun {
568571
return nil
569572
}
@@ -581,7 +584,7 @@ func setClusterClassPause(proxy Proxy, clusterclasses []*node, pause bool, dryRu
581584

582585
// Nb. The operation is wrapped in a retry loop to make setClusterClassPause more resilient to unexpected conditions.
583586
if err := retryWithExponentialBackoff(setClusterClassPauseBackoff, func() error {
584-
return pauseClusterClass(proxy, clusterclass, pause)
587+
return pauseClusterClass(proxy, clusterclass, pause, mutators)
585588
}); err != nil {
586589
return errors.Wrapf(err, "error updating ClusterClass %s/%s", clusterclass.identity.Namespace, clusterclass.identity.Name)
587590
}
@@ -590,19 +593,23 @@ func setClusterClassPause(proxy Proxy, clusterclasses []*node, pause bool, dryRu
590593
}
591594

592595
// patchCluster applies a patch to a node referring to a Cluster object.
593-
func patchCluster(proxy Proxy, cluster *node, patch client.Patch) error {
596+
func patchCluster(proxy Proxy, n *node, patch client.Patch, mutators []ResourceMutatorFunc) error {
594597
cFrom, err := proxy.NewClient()
595598
if err != nil {
596599
return err
597600
}
598601

599-
clusterObj := &clusterv1.Cluster{}
600-
clusterObjKey := client.ObjectKey{
601-
Namespace: cluster.identity.Namespace,
602-
Name: cluster.identity.Name,
602+
// Get the ClusterClass from the server
603+
clusterObj := &unstructured.Unstructured{}
604+
clusterObj.SetAPIVersion(clusterv1.GroupVersion.String())
605+
clusterObj.SetKind(clusterv1.KindCluster)
606+
clusterObj.SetName(n.identity.Name)
607+
clusterObj.SetNamespace(n.identity.Namespace)
608+
for _, mutator := range mutators {
609+
mutator(clusterObj)
603610
}
604611

605-
if err := cFrom.Get(ctx, clusterObjKey, clusterObj); err != nil {
612+
if err := cFrom.Get(ctx, client.ObjectKeyFromObject(clusterObj), clusterObj); err != nil {
606613
return errors.Wrapf(err, "error reading Cluster %s/%s",
607614
clusterObj.GetNamespace(), clusterObj.GetName())
608615
}
@@ -615,19 +622,22 @@ func patchCluster(proxy Proxy, cluster *node, patch client.Patch) error {
615622
return nil
616623
}
617624

618-
func pauseClusterClass(proxy Proxy, n *node, pause bool) error {
625+
func pauseClusterClass(proxy Proxy, n *node, pause bool, mutators []ResourceMutatorFunc) error {
619626
cFrom, err := proxy.NewClient()
620627
if err != nil {
621628
return errors.Wrap(err, "error creating client")
622629
}
623630

624631
// Get the ClusterClass from the server
625-
clusterClass := &clusterv1.ClusterClass{}
626-
clusterClassObjKey := client.ObjectKey{
627-
Name: n.identity.Name,
628-
Namespace: n.identity.Namespace,
629-
}
630-
if err := cFrom.Get(ctx, clusterClassObjKey, clusterClass); err != nil {
632+
clusterClass := &unstructured.Unstructured{}
633+
clusterClass.SetAPIVersion(clusterv1.GroupVersion.String())
634+
clusterClass.SetKind(clusterv1.KindClusterClass)
635+
clusterClass.SetName(n.identity.Name)
636+
clusterClass.SetNamespace(n.identity.Namespace)
637+
for _, mutator := range mutators {
638+
mutator(clusterClass)
639+
}
640+
if err := cFrom.Get(ctx, client.ObjectKeyFromObject(clusterClass), clusterClass); err != nil {
631641
return errors.Wrapf(err, "error reading ClusterClass %s/%s", n.identity.Namespace, n.identity.Name)
632642
}
633643

@@ -740,7 +750,7 @@ func (o *objectMover) ensureNamespace(toProxy Proxy, namespace string) error {
740750
return err
741751
}
742752

743-
// If the namespace does not exists, create it.
753+
// If the namespace does not exist, create it.
744754
ns = &corev1.Namespace{
745755
TypeMeta: metav1.TypeMeta{
746756
APIVersion: "v1",
@@ -758,15 +768,18 @@ func (o *objectMover) ensureNamespace(toProxy Proxy, namespace string) error {
758768
}
759769

760770
// createGroup creates all the Kubernetes objects into the target management cluster corresponding to the object graph nodes in a moveGroup.
761-
func (o *objectMover) createGroup(group moveGroup, toProxy Proxy) error {
771+
func (o *objectMover) createGroup(group moveGroup, toProxy Proxy, mutators []ResourceMutatorFunc) error {
762772
createTargetObjectBackoff := newWriteBackoff()
763773
errList := []error{}
764774

775+
// Maintain a cache of namespaces that have been verified to already exist.
776+
// Nb. This prevents us from making repetitive (and expensive) calls in listing all namespaces to ensure a namespace exists before creating a resource.
777+
ensuredNamespaces := sets.New[string]()
765778
for _, nodeToCreate := range group {
766779
// Creates the Kubernetes object corresponding to the nodeToCreate.
767780
// Nb. The operation is wrapped in a retry loop to make move more resilient to unexpected conditions.
768781
err := retryWithExponentialBackoff(createTargetObjectBackoff, func() error {
769-
return o.createTargetObject(nodeToCreate, toProxy)
782+
return o.createTargetObject(nodeToCreate, toProxy, mutators, ensuredNamespaces)
770783
})
771784
if err != nil {
772785
errList = append(errList, err)
@@ -825,7 +838,7 @@ func (o *objectMover) restoreGroup(group moveGroup, toProxy Proxy) error {
825838
}
826839

827840
// createTargetObject creates the Kubernetes object in the target Management cluster corresponding to the object graph node, taking care of restoring the OwnerReference with the owner nodes, if any.
828-
func (o *objectMover) createTargetObject(nodeToCreate *node, toProxy Proxy) error {
841+
func (o *objectMover) createTargetObject(nodeToCreate *node, toProxy Proxy, mutators []ResourceMutatorFunc, ensuredNamespaces sets.Set[string]) error {
829842
log := logf.Log
830843
log.V(1).Info("Creating", nodeToCreate.identity.Kind, nodeToCreate.identity.Name, "Namespace", nodeToCreate.identity.Namespace)
831844

@@ -858,7 +871,7 @@ func (o *objectMover) createTargetObject(nodeToCreate *node, toProxy Proxy) erro
858871
// Removes current OwnerReferences
859872
obj.SetOwnerReferences(nil)
860873

861-
// Rebuild the owne reference chain
874+
// Rebuild the owner reference chain
862875
o.buildOwnerChain(obj, nodeToCreate)
863876

864877
// FIXME Workaround for https://github.com/kubernetes/kubernetes/issues/32220. Remove when the issue is fixed.
@@ -873,6 +886,15 @@ func (o *objectMover) createTargetObject(nodeToCreate *node, toProxy Proxy) erro
873886
return err
874887
}
875888

889+
for _, mutator := range mutators {
890+
mutator(obj)
891+
}
892+
// Applying mutators MAY change the namespace, so ensure the namespace exists before creating the resource.
893+
if !nodeToCreate.isGlobal && !ensuredNamespaces.Has(obj.GetNamespace()) {
894+
if err = o.ensureNamespace(toProxy, obj.GetNamespace()); err != nil {
895+
return err
896+
}
897+
}
876898
oldManagedFields := obj.GetManagedFields()
877899
if err := cTo.Create(ctx, obj); err != nil {
878900
if !apierrors.IsAlreadyExists(err) {

0 commit comments

Comments
 (0)