Skip to content

Commit 33d0e8f

Browse files
authored
Merge pull request #6858 from mercedes-benz/tobiasgiese/clusterctl-kcp-rollout
✨ Add KCP feature to clusterctl alpha rollout
2 parents aef77c3 + 4593c3c commit 33d0e8f

File tree

19 files changed

+521
-47
lines changed

19 files changed

+521
-47
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
Copyright 2022 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 alpha
18+
19+
import (
20+
"fmt"
21+
"time"
22+
23+
"github.com/pkg/errors"
24+
"k8s.io/apimachinery/pkg/types"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
27+
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
28+
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
29+
)
30+
31+
// getKubeadmControlPlane retrieves the KubeadmControlPlane object corresponding to the name and namespace specified.
32+
func getKubeadmControlPlane(proxy cluster.Proxy, name, namespace string) (*controlplanev1.KubeadmControlPlane, error) {
33+
kcpObj := &controlplanev1.KubeadmControlPlane{}
34+
c, err := proxy.NewClient()
35+
if err != nil {
36+
return nil, err
37+
}
38+
kcpObjKey := client.ObjectKey{
39+
Namespace: namespace,
40+
Name: name,
41+
}
42+
if err := c.Get(ctx, kcpObjKey, kcpObj); err != nil {
43+
return nil, errors.Wrapf(err, "failed to get KubeadmControlPlane %s/%s",
44+
kcpObjKey.Namespace, kcpObjKey.Name)
45+
}
46+
return kcpObj, nil
47+
}
48+
49+
// setRolloutAfter sets KubeadmControlPlane.spec.rolloutAfter.
50+
func setRolloutAfter(proxy cluster.Proxy, name, namespace string) error {
51+
patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf(`{"spec":{"rolloutAfter":"%v"}}`, time.Now().Format(time.RFC3339))))
52+
return patchKubeadmControlPlane(proxy, name, namespace, patch)
53+
}
54+
55+
// patchKubeadmControlPlane applies a patch to a KubeadmControlPlane.
56+
func patchKubeadmControlPlane(proxy cluster.Proxy, name, namespace string, patch client.Patch) error {
57+
cFrom, err := proxy.NewClient()
58+
if err != nil {
59+
return err
60+
}
61+
kcpObj := &controlplanev1.KubeadmControlPlane{}
62+
kcpObjKey := client.ObjectKey{
63+
Namespace: namespace,
64+
Name: name,
65+
}
66+
if err := cFrom.Get(ctx, kcpObjKey, kcpObj); err != nil {
67+
return errors.Wrapf(err, "failed to get KubeadmControlPlane %s/%s", kcpObj.GetNamespace(), kcpObj.GetName())
68+
}
69+
70+
if err := cFrom.Patch(ctx, kcpObj, patch); err != nil {
71+
return errors.Wrapf(err, "failed while patching KubeadmControlPlane %s/%s", kcpObj.GetNamespace(), kcpObj.GetName())
72+
}
73+
return nil
74+
}

cmd/clusterctl/client/alpha/machinedeployment.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@ limitations under the License.
1717
package alpha
1818

1919
import (
20+
"fmt"
2021
"strconv"
22+
"time"
2123

2224
"github.com/pkg/errors"
2325
"k8s.io/apimachinery/pkg/api/meta"
2426
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2527
"k8s.io/apimachinery/pkg/labels"
2628
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/apimachinery/pkg/types"
2730
"sigs.k8s.io/controller-runtime/pkg/client"
2831

2932
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -43,12 +46,18 @@ func getMachineDeployment(proxy cluster.Proxy, name, namespace string) (*cluster
4346
Name: name,
4447
}
4548
if err := c.Get(ctx, mdObjKey, mdObj); err != nil {
46-
return nil, errors.Wrapf(err, "error reading MachineDeployment %s/%s",
49+
return nil, errors.Wrapf(err, "failed to get MachineDeployment %s/%s",
4750
mdObjKey.Namespace, mdObjKey.Name)
4851
}
4952
return mdObj, nil
5053
}
5154

55+
// setRestartedAtAnnotation sets the restartedAt annotation in the MachineDeployment's spec.template.objectmeta.
56+
func setRestartedAtAnnotation(proxy cluster.Proxy, name, namespace string) error {
57+
patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf(`{"spec":{"template":{"metadata":{"annotations":{"cluster.x-k8s.io/restartedAt":"%v"}}}}}`, time.Now().Format(time.RFC3339))))
58+
return patchMachineDeployment(proxy, name, namespace, patch)
59+
}
60+
5261
// patchMachineDeployment applies a patch to a machinedeployment.
5362
func patchMachineDeployment(proxy cluster.Proxy, name, namespace string, patch client.Patch) error {
5463
cFrom, err := proxy.NewClient()
@@ -61,11 +70,11 @@ func patchMachineDeployment(proxy cluster.Proxy, name, namespace string, patch c
6170
Name: name,
6271
}
6372
if err := cFrom.Get(ctx, mdObjKey, mdObj); err != nil {
64-
return errors.Wrapf(err, "error reading MachineDeployment %s/%s", mdObj.GetNamespace(), mdObj.GetName())
73+
return errors.Wrapf(err, "failed to get MachineDeployment %s/%s", mdObj.GetNamespace(), mdObj.GetName())
6574
}
6675

6776
if err := cFrom.Patch(ctx, mdObj, patch); err != nil {
68-
return errors.Wrapf(err, "error while patching MachineDeployment %s/%s", mdObj.GetNamespace(), mdObj.GetName())
77+
return errors.Wrapf(err, "failed while patching MachineDeployment %s/%s", mdObj.GetNamespace(), mdObj.GetName())
6978
}
7079
return nil
7180
}

cmd/clusterctl/client/alpha/rollout.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,17 @@ import (
2222
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
2323
)
2424

25-
// MachineDeployment is a resource type.
26-
const MachineDeployment = "machinedeployment"
25+
const (
26+
// MachineDeployment is a resource type.
27+
MachineDeployment = "machinedeployment"
28+
// KubeadmControlPlane is a resource type.
29+
KubeadmControlPlane = "kubeadmcontrolplane"
30+
)
2731

28-
var validResourceTypes = []string{MachineDeployment}
32+
var validResourceTypes = []string{
33+
MachineDeployment,
34+
KubeadmControlPlane,
35+
}
2936

3037
// Rollout defines the behavior of a rollout implementation.
3138
type Rollout interface {

cmd/clusterctl/client/alpha/rollout_pauser.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import (
2424
"k8s.io/apimachinery/pkg/types"
2525
"sigs.k8s.io/controller-runtime/pkg/client"
2626

27+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2728
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
29+
"sigs.k8s.io/cluster-api/util/annotations"
2830
)
2931

3032
// 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)
4143
if err := pauseMachineDeployment(proxy, ref.Name, ref.Namespace); err != nil {
4244
return err
4345
}
46+
case KubeadmControlPlane:
47+
kcp, err := getKubeadmControlPlane(proxy, ref.Name, ref.Namespace)
48+
if err != nil || kcp == nil {
49+
return errors.Wrapf(err, "failed to fetch %v/%v", ref.Kind, ref.Name)
50+
}
51+
if annotations.HasPaused(kcp.GetObjectMeta()) {
52+
return errors.Errorf("KubeadmControlPlane is already paused: %v/%v\n", ref.Kind, ref.Name) //nolint:revive // KubeadmControlPlane is intentionally capitalized.
53+
}
54+
if err := pauseKubeadmControlPlane(proxy, ref.Name, ref.Namespace); err != nil {
55+
return err
56+
}
4457
default:
4558
return errors.Errorf("Invalid resource type %q, valid values are %v", ref.Kind, validResourceTypes)
4659
}
@@ -52,3 +65,9 @@ func pauseMachineDeployment(proxy cluster.Proxy, name, namespace string) error {
5265
patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf("{\"spec\":{\"paused\":%t}}", true)))
5366
return patchMachineDeployment(proxy, name, namespace, patch)
5467
}
68+
69+
// pauseKubeadmControlPlane sets paused annotation to true.
70+
func pauseKubeadmControlPlane(proxy cluster.Proxy, name, namespace string) error {
71+
patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf("{\"metadata\":{\"annotations\":{%q: \"%t\"}}}", clusterv1.PausedAnnotation, true)))
72+
return patchKubeadmControlPlane(proxy, name, namespace, patch)
73+
}

cmd/clusterctl/client/alpha/rollout_pauser_test.go

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import (
2727

2828
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2929
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
30+
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
31+
"sigs.k8s.io/cluster-api/util/annotations"
3032
)
3133

3234
func Test_ObjectPauser(t *testing.T) {
@@ -89,6 +91,55 @@ func Test_ObjectPauser(t *testing.T) {
8991
wantErr: true,
9092
wantPaused: false,
9193
},
94+
{
95+
name: "kubeadmcontrolplane should be paused",
96+
fields: fields{
97+
objs: []client.Object{
98+
&controlplanev1.KubeadmControlPlane{
99+
TypeMeta: metav1.TypeMeta{
100+
Kind: "KubeadmControlPlane",
101+
},
102+
ObjectMeta: metav1.ObjectMeta{
103+
Namespace: "default",
104+
Name: "kcp",
105+
},
106+
},
107+
},
108+
ref: corev1.ObjectReference{
109+
Kind: KubeadmControlPlane,
110+
Name: "kcp",
111+
Namespace: "default",
112+
},
113+
},
114+
wantErr: false,
115+
wantPaused: true,
116+
},
117+
{
118+
name: "re-pausing an already paused kubeadmcontrolplane should return error",
119+
fields: fields{
120+
objs: []client.Object{
121+
&controlplanev1.KubeadmControlPlane{
122+
TypeMeta: metav1.TypeMeta{
123+
Kind: "KubeadmControlPlane",
124+
},
125+
ObjectMeta: metav1.ObjectMeta{
126+
Namespace: "default",
127+
Name: "kcp",
128+
Annotations: map[string]string{
129+
clusterv1.PausedAnnotation: "true",
130+
},
131+
},
132+
},
133+
},
134+
ref: corev1.ObjectReference{
135+
Kind: KubeadmControlPlane,
136+
Name: "kcp",
137+
Namespace: "default",
138+
},
139+
},
140+
wantErr: true,
141+
wantPaused: false,
142+
},
92143
}
93144
for _, tt := range tests {
94145
t.Run(tt.name, func(t *testing.T) {
@@ -105,10 +156,18 @@ func Test_ObjectPauser(t *testing.T) {
105156
cl, err := proxy.NewClient()
106157
g.Expect(err).ToNot(HaveOccurred())
107158
key := client.ObjectKeyFromObject(obj)
108-
md := &clusterv1.MachineDeployment{}
109-
err = cl.Get(context.TODO(), key, md)
110-
g.Expect(err).ToNot(HaveOccurred())
111-
g.Expect(md.Spec.Paused).To(Equal(tt.wantPaused))
159+
switch obj.(type) {
160+
case *clusterv1.MachineDeployment:
161+
md := &clusterv1.MachineDeployment{}
162+
err = cl.Get(context.TODO(), key, md)
163+
g.Expect(err).ToNot(HaveOccurred())
164+
g.Expect(md.Spec.Paused).To(Equal(tt.wantPaused))
165+
case *controlplanev1.KubeadmControlPlane:
166+
kcp := &controlplanev1.KubeadmControlPlane{}
167+
err = cl.Get(context.TODO(), key, kcp)
168+
g.Expect(err).ToNot(HaveOccurred())
169+
g.Expect(annotations.HasPaused(kcp.GetObjectMeta())).To(Equal(tt.wantPaused))
170+
}
112171
}
113172
})
114173
}

cmd/clusterctl/client/alpha/rollout_restarter.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,13 @@ limitations under the License.
1717
package alpha
1818

1919
import (
20-
"fmt"
2120
"time"
2221

2322
"github.com/pkg/errors"
2423
corev1 "k8s.io/api/core/v1"
25-
"k8s.io/apimachinery/pkg/types"
26-
"sigs.k8s.io/controller-runtime/pkg/client"
2724

2825
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
26+
"sigs.k8s.io/cluster-api/util/annotations"
2927
)
3028

3129
// ObjectRestarter will issue a restart on the specified cluster-api resource.
@@ -42,14 +40,22 @@ func (r *rollout) ObjectRestarter(proxy cluster.Proxy, ref corev1.ObjectReferenc
4240
if err := setRestartedAtAnnotation(proxy, ref.Name, ref.Namespace); err != nil {
4341
return err
4442
}
43+
case KubeadmControlPlane:
44+
kcp, err := getKubeadmControlPlane(proxy, ref.Name, ref.Namespace)
45+
if err != nil || kcp == nil {
46+
return errors.Wrapf(err, "failed to fetch %v/%v", ref.Kind, ref.Name)
47+
}
48+
if annotations.HasPaused(kcp.GetObjectMeta()) {
49+
return errors.Errorf("can't restart paused KubeadmControlPlane (remove annotation 'cluster.x-k8s.io/paused' first): %v/%v", ref.Kind, ref.Name)
50+
}
51+
if kcp.Spec.RolloutAfter != nil && kcp.Spec.RolloutAfter.After(time.Now()) {
52+
return errors.Errorf("can't update KubeadmControlPlane (remove 'spec.rolloutAfter' first): %v/%v", ref.Kind, ref.Name)
53+
}
54+
if err := setRolloutAfter(proxy, ref.Name, ref.Namespace); err != nil {
55+
return err
56+
}
4557
default:
4658
return errors.Errorf("Invalid resource type %v. Valid values: %v", ref.Kind, validResourceTypes)
4759
}
4860
return nil
4961
}
50-
51-
// setRestartedAtAnnotation sets the restartedAt annotation in the MachineDeployment's spec.template.objectmeta.
52-
func setRestartedAtAnnotation(proxy cluster.Proxy, name, namespace string) error {
53-
patch := client.RawPatch(types.MergePatchType, []byte(fmt.Sprintf("{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"cluster.x-k8s.io/restartedAt\":\"%v\"}}}}}", time.Now().Format(time.RFC3339))))
54-
return patchMachineDeployment(proxy, name, namespace, patch)
55-
}

0 commit comments

Comments
 (0)