diff --git a/cmd/clusterctl/client/alpha/kubeadmcontrolplane.go b/cmd/clusterctl/client/alpha/kubeadmcontrolplane.go new file mode 100644 index 000000000000..6f54b5240107 --- /dev/null +++ b/cmd/clusterctl/client/alpha/kubeadmcontrolplane.go @@ -0,0 +1,64 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package alpha + +import ( + "github.com/pkg/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" + kubeadmv1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" +) + +// getKubeadmControlPlane retrieves the KubeadmControlPlane object corresponding to the name and namespace specified. +func getKubeadmControlPlane(proxy cluster.Proxy, name, namespace string) (*kubeadmv1.KubeadmControlPlane, error) { + mdObj := &kubeadmv1.KubeadmControlPlane{} + c, err := proxy.NewClient() + if err != nil { + return nil, err + } + mdObjKey := client.ObjectKey{ + Namespace: namespace, + Name: name, + } + if err := c.Get(ctx, mdObjKey, mdObj); err != nil { + return nil, errors.Wrapf(err, "error reading KubeadmControlPlane %s/%s", + mdObjKey.Namespace, mdObjKey.Name) + } + return mdObj, nil +} + +// patchKubeadmControlPlane applies a patch to a KubeadmControlPlane. +func patchKubeadmControlPlane(proxy cluster.Proxy, name, namespace string, patch client.Patch) error { + cFrom, err := proxy.NewClient() + if err != nil { + return err + } + mdObj := &kubeadmv1.KubeadmControlPlane{} + mdObjKey := client.ObjectKey{ + Namespace: namespace, + Name: name, + } + if err := cFrom.Get(ctx, mdObjKey, mdObj); err != nil { + return errors.Wrapf(err, "error reading KubeadmControlPlane %s/%s", mdObj.GetNamespace(), mdObj.GetName()) + } + + if err := cFrom.Patch(ctx, mdObj, patch); err != nil { + return errors.Wrapf(err, "error while patching KubeadmControlPlane %s/%s", mdObj.GetNamespace(), mdObj.GetName()) + } + return nil +} diff --git a/cmd/clusterctl/client/alpha/rollout.go b/cmd/clusterctl/client/alpha/rollout.go index bc125878fd6f..b9e5f62bf036 100644 --- a/cmd/clusterctl/client/alpha/rollout.go +++ b/cmd/clusterctl/client/alpha/rollout.go @@ -24,8 +24,12 @@ import ( // MachineDeployment is a resource type. const MachineDeployment = "machinedeployment" +const KubeadmControlPlane = "kubeadmcontrolplane" -var validResourceTypes = []string{MachineDeployment} +var validResourceTypes = []string{ + MachineDeployment, + KubeadmControlPlane, +} // Rollout defines the behavior of a rollout implementation. type Rollout interface { diff --git a/cmd/clusterctl/client/alpha/rollout_pauser.go b/cmd/clusterctl/client/alpha/rollout_pauser.go index 0e5619bb4eb6..b36d70775245 100644 --- a/cmd/clusterctl/client/alpha/rollout_pauser.go +++ b/cmd/clusterctl/client/alpha/rollout_pauser.go @@ -24,7 +24,9 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" + "sigs.k8s.io/cluster-api/util/annotations" ) // ObjectPauser will issue a pause on the specified cluster-api resource. @@ -41,6 +43,17 @@ func (r *rollout) ObjectPauser(proxy cluster.Proxy, ref corev1.ObjectReference) if err := pauseMachineDeployment(proxy, ref.Name, ref.Namespace); err != nil { return err } + case KubeadmControlPlane: + kcp, err := getKubeadmControlPlane(proxy, ref.Name, ref.Namespace) + if err != nil || kcp == nil { + return errors.Wrapf(err, "failed to fetch %v/%v", ref.Kind, ref.Name) + } + if annotations.HasPaused(kcp.GetObjectMeta()) { + return errors.Errorf("KubeadmControlPlane is already paused: %v/%v\n", ref.Kind, ref.Name) //nolint:revive // KubeadmControlPlane is intentionally capitalized. + } + if err := pauseKubeadmControlPlane(proxy, ref.Name, ref.Namespace); err != nil { + return err + } default: return errors.Errorf("Invalid resource type %q, valid values are %v", ref.Kind, validResourceTypes) } @@ -52,3 +65,9 @@ func pauseMachineDeployment(proxy cluster.Proxy, name, namespace string) error { patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf("{\"spec\":{\"paused\":%t}}", true))) return patchMachineDeployment(proxy, name, namespace, patch) } + +// pauseKubeadmControlPlane sets Paused to true in the KubeadmControlPlane's spec. +func pauseKubeadmControlPlane(proxy cluster.Proxy, name, namespace string) error { + patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf("{\"metadata\":{\"annotations\":{\"%s\": \"%t\"}}}", clusterv1.PausedAnnotation, true))) + return patchKubeadmControlPlane(proxy, name, namespace, patch) +} diff --git a/cmd/clusterctl/client/alpha/rollout_pauser_test.go b/cmd/clusterctl/client/alpha/rollout_pauser_test.go index 9a1473c38902..bbe0775f82d4 100644 --- a/cmd/clusterctl/client/alpha/rollout_pauser_test.go +++ b/cmd/clusterctl/client/alpha/rollout_pauser_test.go @@ -27,6 +27,8 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" + kubeadmv1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" + "sigs.k8s.io/cluster-api/util/annotations" ) func Test_ObjectPauser(t *testing.T) { @@ -36,12 +38,14 @@ func Test_ObjectPauser(t *testing.T) { } tests := []struct { name string + apiType string fields fields wantErr bool wantPaused bool }{ { - name: "machinedeployment should be paused", + name: "machinedeployment should be paused", + apiType: MachineDeployment, fields: fields{ objs: []client.Object{ &clusterv1.MachineDeployment{ @@ -64,7 +68,8 @@ func Test_ObjectPauser(t *testing.T) { wantPaused: true, }, { - name: "re-pausing an already paused machinedeployment should return error", + name: "re-pausing an already paused machinedeployment should return error", + apiType: MachineDeployment, fields: fields{ objs: []client.Object{ &clusterv1.MachineDeployment{ @@ -89,6 +94,57 @@ func Test_ObjectPauser(t *testing.T) { wantErr: true, wantPaused: false, }, + { + name: "kubeadmcontrolplane should be paused", + apiType: KubeadmControlPlane, + fields: fields{ + objs: []client.Object{ + &kubeadmv1.KubeadmControlPlane{ + TypeMeta: metav1.TypeMeta{ + Kind: "KubeadmControlPlane", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "kcp", + }, + }, + }, + ref: corev1.ObjectReference{ + Kind: KubeadmControlPlane, + Name: "kcp", + Namespace: "default", + }, + }, + wantErr: false, + wantPaused: true, + }, + { + name: "re-pausing an already paused kubeadmcontrolplane should return error", + apiType: KubeadmControlPlane, + fields: fields{ + objs: []client.Object{ + &kubeadmv1.KubeadmControlPlane{ + TypeMeta: metav1.TypeMeta{ + Kind: "KubeadmControlPlane", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "kcp", + Annotations: map[string]string{ + clusterv1.PausedAnnotation: "true", + }, + }, + }, + }, + ref: corev1.ObjectReference{ + Kind: KubeadmControlPlane, + Name: "kcp", + Namespace: "default", + }, + }, + wantErr: true, + wantPaused: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -105,10 +161,18 @@ func Test_ObjectPauser(t *testing.T) { cl, err := proxy.NewClient() g.Expect(err).ToNot(HaveOccurred()) key := client.ObjectKeyFromObject(obj) - md := &clusterv1.MachineDeployment{} - err = cl.Get(context.TODO(), key, md) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(md.Spec.Paused).To(Equal(tt.wantPaused)) + switch tt.apiType { + case MachineDeployment: + md := &clusterv1.MachineDeployment{} + err = cl.Get(context.TODO(), key, md) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(md.Spec.Paused).To(Equal(tt.wantPaused)) + case KubeadmControlPlane: + kcp := &kubeadmv1.KubeadmControlPlane{} + err = cl.Get(context.TODO(), key, kcp) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(annotations.HasPaused(kcp.GetObjectMeta())).To(Equal(tt.wantPaused)) + } } }) } diff --git a/cmd/clusterctl/client/alpha/rollout_restarter.go b/cmd/clusterctl/client/alpha/rollout_restarter.go index f787505b787f..19f94af888bb 100644 --- a/cmd/clusterctl/client/alpha/rollout_restarter.go +++ b/cmd/clusterctl/client/alpha/rollout_restarter.go @@ -26,6 +26,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" + "sigs.k8s.io/cluster-api/util/annotations" ) // ObjectRestarter will issue a restart on the specified cluster-api resource. @@ -42,6 +43,17 @@ func (r *rollout) ObjectRestarter(proxy cluster.Proxy, ref corev1.ObjectReferenc if err := setRestartedAtAnnotation(proxy, ref.Name, ref.Namespace); err != nil { return err } + case KubeadmControlPlane: + kcp, err := getKubeadmControlPlane(proxy, ref.Name, ref.Namespace) + if err != nil || kcp == nil { + return errors.Wrapf(err, "failed to fetch %v/%v", ref.Kind, ref.Name) + } + if annotations.HasPaused(kcp.GetObjectMeta()) { + return errors.Errorf("can't restart paused KubeadmControlPlane (remove annotation 'cluster.x-k8s.io/paused' first): %v/%v", ref.Kind, ref.Name) + } + if err := setRolloutAfter(proxy, ref.Name, ref.Namespace); err != nil { + return err + } default: return errors.Errorf("Invalid resource type %v. Valid values: %v", ref.Kind, validResourceTypes) } @@ -53,3 +65,8 @@ func setRestartedAtAnnotation(proxy cluster.Proxy, name, namespace string) error patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf("{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"cluster.x-k8s.io/restartedAt\":\"%v\"}}}}}", time.Now().Format(time.RFC3339)))) return patchMachineDeployment(proxy, name, namespace, patch) } + +func setRolloutAfter(proxy cluster.Proxy, name, namespace string) error { + patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf("{\"spec\":{\"rolloutAfter\":\"%v\"}}", time.Now().Format(time.RFC3339)))) + return patchKubeadmControlPlane(proxy, name, namespace, patch) +} diff --git a/cmd/clusterctl/client/alpha/rollout_restarter_test.go b/cmd/clusterctl/client/alpha/rollout_restarter_test.go index 05c408a4d94c..e099a362227d 100644 --- a/cmd/clusterctl/client/alpha/rollout_restarter_test.go +++ b/cmd/clusterctl/client/alpha/rollout_restarter_test.go @@ -27,6 +27,7 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" + kubeadmv1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" ) func Test_ObjectRestarter(t *testing.T) { @@ -35,13 +36,15 @@ func Test_ObjectRestarter(t *testing.T) { ref corev1.ObjectReference } tests := []struct { - name string - fields fields - wantErr bool - wantAnnotation bool + name string + apiType string + fields fields + wantErr bool + wantRollout bool }{ { - name: "machinedeployment should have restart annotation", + name: "machinedeployment should have restart annotation", + apiType: MachineDeployment, fields: fields{ objs: []client.Object{ &clusterv1.MachineDeployment{ @@ -61,11 +64,12 @@ func Test_ObjectRestarter(t *testing.T) { Namespace: "default", }, }, - wantErr: false, - wantAnnotation: true, + wantErr: false, + wantRollout: true, }, { - name: "paused machinedeployment should not have restart annotation", + name: "paused machinedeployment should not have restart annotation", + apiType: MachineDeployment, fields: fields{ objs: []client.Object{ &clusterv1.MachineDeployment{ @@ -88,8 +92,61 @@ func Test_ObjectRestarter(t *testing.T) { Namespace: "default", }, }, - wantErr: true, - wantAnnotation: false, + wantErr: true, + wantRollout: false, + }, + { + name: "kubeadmcontrolplane should have rolloutAfter", + apiType: KubeadmControlPlane, + fields: fields{ + objs: []client.Object{ + &kubeadmv1.KubeadmControlPlane{ + TypeMeta: metav1.TypeMeta{ + Kind: "KubeadmControlPlane", + APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "kcp", + }, + }, + }, + ref: corev1.ObjectReference{ + Kind: KubeadmControlPlane, + Name: "kcp", + Namespace: "default", + }, + }, + wantErr: false, + wantRollout: true, + }, + { + name: "paused kubeadmcontrolplane should not have rolloutAfter", + apiType: KubeadmControlPlane, + fields: fields{ + objs: []client.Object{ + &kubeadmv1.KubeadmControlPlane{ + TypeMeta: metav1.TypeMeta{ + Kind: "KubeadmControlPlane", + APIVersion: "controlplane.cluster.x-k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "kcp", + Annotations: map[string]string{ + clusterv1.PausedAnnotation: "true", + }, + }, + }, + }, + ref: corev1.ObjectReference{ + Kind: KubeadmControlPlane, + Name: "kcp", + Namespace: "default", + }, + }, + wantErr: true, + wantRollout: false, }, } for _, tt := range tests { @@ -107,13 +164,25 @@ func Test_ObjectRestarter(t *testing.T) { cl, err := proxy.NewClient() g.Expect(err).ToNot(HaveOccurred()) key := client.ObjectKeyFromObject(obj) - md := &clusterv1.MachineDeployment{} - err = cl.Get(context.TODO(), key, md) - g.Expect(err).ToNot(HaveOccurred()) - if tt.wantAnnotation { - g.Expect(md.Spec.Template.Annotations).To(HaveKey("cluster.x-k8s.io/restartedAt")) - } else { - g.Expect(md.Spec.Template.Annotations).ToNot(HaveKey("cluster.x-k8s.io/restartedAt")) + switch tt.apiType { + case MachineDeployment: + md := &clusterv1.MachineDeployment{} + err = cl.Get(context.TODO(), key, md) + g.Expect(err).ToNot(HaveOccurred()) + if tt.wantRollout { + g.Expect(md.Spec.Template.Annotations).To(HaveKey("cluster.x-k8s.io/restartedAt")) + } else { + g.Expect(md.Spec.Template.Annotations).ToNot(HaveKey("cluster.x-k8s.io/restartedAt")) + } + case KubeadmControlPlane: + kcp := &kubeadmv1.KubeadmControlPlane{} + err = cl.Get(context.TODO(), key, kcp) + g.Expect(err).ToNot(HaveOccurred()) + if tt.wantRollout { + g.Expect(kcp.Spec.RolloutAfter).NotTo(BeNil()) + } else { + g.Expect(kcp.Spec.RolloutAfter).To(nil) + } } } }) diff --git a/cmd/clusterctl/client/alpha/rollout_resumer.go b/cmd/clusterctl/client/alpha/rollout_resumer.go index e401f9db97ae..1384fc88bbad 100644 --- a/cmd/clusterctl/client/alpha/rollout_resumer.go +++ b/cmd/clusterctl/client/alpha/rollout_resumer.go @@ -18,13 +18,16 @@ package alpha import ( "fmt" + "strings" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" + "sigs.k8s.io/cluster-api/util/annotations" ) // ObjectResumer will issue a resume on the specified cluster-api resource. @@ -41,6 +44,17 @@ func (r *rollout) ObjectResumer(proxy cluster.Proxy, ref corev1.ObjectReference) if err := resumeMachineDeployment(proxy, ref.Name, ref.Namespace); err != nil { return err } + case KubeadmControlPlane: + kcp, err := getKubeadmControlPlane(proxy, ref.Name, ref.Namespace) + if err != nil || kcp == nil { + return errors.Wrapf(err, "failed to fetch %v/%v", ref.Kind, ref.Name) + } + if !annotations.HasPaused(kcp.GetObjectMeta()) { + return errors.Errorf("KubeadmControlPlane is not currently paused: %v/%v\n", ref.Kind, ref.Name) //nolint:revive // KubeadmControlPlane is intentionally capitalized. + } + if err := resumeKubeadmControlPlane(proxy, ref.Name, ref.Namespace); err != nil { + return err + } default: return errors.Errorf("invalid resource type %q, valid values are %v", ref.Kind, validResourceTypes) } @@ -53,3 +67,11 @@ func resumeMachineDeployment(proxy cluster.Proxy, name, namespace string) error return patchMachineDeployment(proxy, name, namespace, patch) } + +// pauseKubeadmControlPlane sets Paused to true in the KubeadmControlPlane's spec. +func resumeKubeadmControlPlane(proxy cluster.Proxy, name, namespace string) error { + pausedAnnotation := strings.Replace(clusterv1.PausedAnnotation, "/", "~1", -1) + patch := client.RawPatch(types.JSONPatchType, []byte(fmt.Sprintf("[{\"op\": \"remove\", \"path\": \"/metadata/annotations/%s\"}]", pausedAnnotation))) + + return patchKubeadmControlPlane(proxy, name, namespace, patch) +} diff --git a/cmd/clusterctl/client/alpha/rollout_resumer_test.go b/cmd/clusterctl/client/alpha/rollout_resumer_test.go index 5b585264b517..6021e0591f0b 100644 --- a/cmd/clusterctl/client/alpha/rollout_resumer_test.go +++ b/cmd/clusterctl/client/alpha/rollout_resumer_test.go @@ -27,6 +27,8 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test" + kubeadmv1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" + "sigs.k8s.io/cluster-api/util/annotations" ) func Test_ObjectResumer(t *testing.T) { @@ -36,12 +38,14 @@ func Test_ObjectResumer(t *testing.T) { } tests := []struct { name string + apiType string fields fields wantErr bool wantPaused bool }{ { - name: "paused machinedeployment should be unpaused", + name: "paused machinedeployment should be unpaused", + apiType: MachineDeployment, fields: fields{ objs: []client.Object{ &clusterv1.MachineDeployment{ @@ -67,7 +71,8 @@ func Test_ObjectResumer(t *testing.T) { wantPaused: false, }, { - name: "unpausing an already unpaused machinedeployment should return error", + name: "unpausing an already unpaused machinedeployment should return error", + apiType: MachineDeployment, fields: fields{ objs: []client.Object{ &clusterv1.MachineDeployment{ @@ -92,6 +97,57 @@ func Test_ObjectResumer(t *testing.T) { wantErr: true, wantPaused: false, }, + { + name: "paused kubeadmcontrolplane should be unpaused", + apiType: KubeadmControlPlane, + fields: fields{ + objs: []client.Object{ + &kubeadmv1.KubeadmControlPlane{ + TypeMeta: metav1.TypeMeta{ + Kind: "KubeadmControlPlane", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "kcp", + Annotations: map[string]string{ + clusterv1.PausedAnnotation: "true", + }, + }, + }, + }, + ref: corev1.ObjectReference{ + Kind: KubeadmControlPlane, + Name: "kcp", + Namespace: "default", + }, + }, + wantErr: false, + wantPaused: false, + }, + { + name: "unpausing an already unpaused kubeadmcontrolplane should return error", + apiType: KubeadmControlPlane, + fields: fields{ + objs: []client.Object{ + &kubeadmv1.KubeadmControlPlane{ + TypeMeta: metav1.TypeMeta{ + Kind: "KubeadmControlPlane", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "kcp", + }, + }, + }, + ref: corev1.ObjectReference{ + Kind: KubeadmControlPlane, + Name: "kcp", + Namespace: "default", + }, + }, + wantErr: true, + wantPaused: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -108,10 +164,18 @@ func Test_ObjectResumer(t *testing.T) { cl, err := proxy.NewClient() g.Expect(err).ToNot(HaveOccurred()) key := client.ObjectKeyFromObject(obj) - md := &clusterv1.MachineDeployment{} - err = cl.Get(context.TODO(), key, md) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(md.Spec.Paused).To(Equal(tt.wantPaused)) + switch tt.apiType { + case MachineDeployment: + md := &clusterv1.MachineDeployment{} + err = cl.Get(context.TODO(), key, md) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(md.Spec.Paused).To(Equal(tt.wantPaused)) + case KubeadmControlPlane: + kcp := &kubeadmv1.KubeadmControlPlane{} + err = cl.Get(context.TODO(), key, kcp) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(annotations.HasPaused(kcp.GetObjectMeta())).To(Equal(tt.wantPaused)) + } } }) } diff --git a/cmd/clusterctl/cmd/rollout/pause.go b/cmd/clusterctl/cmd/rollout/pause.go index 413d9432dba6..50a29391c79f 100644 --- a/cmd/clusterctl/cmd/rollout/pause.go +++ b/cmd/clusterctl/cmd/rollout/pause.go @@ -38,12 +38,14 @@ var ( pauseLong = templates.LongDesc(` Mark the provided cluster-api resource as paused. - Paused resources will not be reconciled by a controller. Use "clusterctl alpha rollout resume" to resume a paused resource. Currently only MachineDeployments support being paused.`) + Paused resources will not be reconciled by a controller. Use "clusterctl alpha rollout resume" to resume a paused resource. Currently only MachineDeployments and KubeadmControlPlanes support being paused.`) pauseExample = templates.Examples(` # Mark the machinedeployment as paused. clusterctl alpha rollout pause machinedeployment/my-md-0 -`) + + # Restart a kubeadmcontrolplane + clusterctl alpha rollout pause kubeadmcontrolplane/my-kcp`) ) // NewCmdRolloutPause returns a Command instance for 'rollout pause' sub command. diff --git a/cmd/clusterctl/cmd/rollout/restart.go b/cmd/clusterctl/cmd/rollout/restart.go index 0ac0da147427..e6d85cc4cdbf 100644 --- a/cmd/clusterctl/cmd/rollout/restart.go +++ b/cmd/clusterctl/cmd/rollout/restart.go @@ -41,7 +41,10 @@ var ( restartExample = templates.Examples(` # Restart a machinedeployment - clusterctl alpha rollout restart machinedeployment/my-md-0`) + clusterctl alpha rollout restart machinedeployment/my-md-0 + + # Restart a kubeadmcontrolplane + clusterctl alpha rollout restart kubeadmcontrolplane/my-kcp`) ) // NewCmdRolloutRestart returns a Command instance for 'rollout restart' sub command. diff --git a/cmd/clusterctl/cmd/rollout/resume.go b/cmd/clusterctl/cmd/rollout/resume.go index 6b6b273ab094..fa47870a6e72 100644 --- a/cmd/clusterctl/cmd/rollout/resume.go +++ b/cmd/clusterctl/cmd/rollout/resume.go @@ -37,11 +37,14 @@ var ( resumeLong = templates.LongDesc(` Resume a paused cluster-api resource - Paused resources will not be reconciled by a controller. By resuming a resource, we allow it to be reconciled again. Currently only MachineDeployments support being resumed.`) + Paused resources will not be reconciled by a controller. By resuming a resource, we allow it to be reconciled again. Currently only MachineDeployments and KubeadmControlPlanes support being resumed.`) resumeExample = templates.Examples(` # Resume an already paused machinedeployment - clusterctl alpha rollout resume machinedeployment/my-md-0`) + clusterctl alpha rollout resume machinedeployment/my-md-0 + + # Restart a kubeadmcontrolplane + clusterctl alpha rollout resume kubeadmcontrolplane/my-kcp`) ) // NewCmdRolloutResume returns a Command instance for 'rollout resume' sub command. diff --git a/cmd/clusterctl/internal/scheme/scheme.go b/cmd/clusterctl/internal/scheme/scheme.go index 531a3755f674..4337d5b311d7 100644 --- a/cmd/clusterctl/internal/scheme/scheme.go +++ b/cmd/clusterctl/internal/scheme/scheme.go @@ -27,6 +27,7 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" + kubeadmv1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1" expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" ) @@ -45,5 +46,6 @@ func init() { _ = admissionregistration.AddToScheme(Scheme) _ = admissionregistrationv1beta1.AddToScheme(Scheme) _ = addonsv1.AddToScheme(Scheme) + _ = kubeadmv1.AddToScheme(Scheme) _ = expv1.AddToScheme(Scheme) } diff --git a/cmd/clusterctl/internal/test/fake_proxy.go b/cmd/clusterctl/internal/test/fake_proxy.go index f4b40bf7fa23..ec6f5eaa3126 100644 --- a/cmd/clusterctl/internal/test/fake_proxy.go +++ b/cmd/clusterctl/internal/test/fake_proxy.go @@ -35,6 +35,7 @@ import ( fakecontrolplane "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test/providers/controlplane" fakeexternal "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test/providers/external" fakeinfrastructure "sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test/providers/infrastructure" + kubeadmv1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1" expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" ) @@ -57,6 +58,7 @@ func init() { _ = expv1.AddToScheme(FakeScheme) _ = addonsv1.AddToScheme(FakeScheme) _ = apiextensionsv1.AddToScheme(FakeScheme) + _ = kubeadmv1.AddToScheme(FakeScheme) _ = fakebootstrap.AddToScheme(FakeScheme) _ = fakecontrolplane.AddToScheme(FakeScheme)