Skip to content

Commit c3876bd

Browse files
committed
feat: Add k8s version logic for external cloud-provider flag
This flag has been removed from kube-apiserver in k8s v1.33 so need to use version specific logic to add it for pre-1.33 clusters.
1 parent 2fa88f6 commit c3876bd

File tree

10 files changed

+323
-38
lines changed

10 files changed

+323
-38
lines changed

charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/aws-cluster-class.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ spec:
8484
clusterConfiguration:
8585
apiServer:
8686
extraArgs:
87-
cloud-provider: external
8887
profiling: "false"
8988
controllerManager:
9089
extraArgs:

charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/nutanix-cluster-class.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ spec:
123123
clusterConfiguration:
124124
apiServer:
125125
extraArgs:
126-
cloud-provider: external
127126
profiling: "false"
128127
tls-cipher-suites: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
129128
controllerManager:

common/pkg/capi/clustertopology/variables/variable.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ func IsNotFoundError(err error) bool {
3333
return topologymutation.IsNotFoundError(err) || errors.As(err, &fieldNotFoundError{})
3434
}
3535

36+
func IsFieldNotFoundError(err error) bool {
37+
return errors.As(err, &fieldNotFoundError{})
38+
}
39+
3640
// Get finds and parses variable to given type.
3741
func Get[T any](
3842
variables map[string]apiextensionsv1.JSON,

common/pkg/testutils/capitest/patches.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,15 @@ func AssertGeneratePatches[T mutation.GeneratePatches](
6363
}
6464
resp := &runtimehooksv1.GeneratePatchesResponse{}
6565
h.GeneratePatches(context.Background(), req, resp)
66-
expectedStatus := runtimehooksv1.ResponseStatusSuccess
6766
if tt.ExpectedFailure {
68-
expectedStatus = runtimehooksv1.ResponseStatusFailure
67+
g.Expect(resp.Status).
68+
To(gomega.Equal(runtimehooksv1.ResponseStatusFailure), fmt.Sprintf("Message: %s", resp.Message))
69+
return
6970
}
71+
7072
g.Expect(resp.Status).
71-
To(gomega.Equal(expectedStatus), fmt.Sprintf("Message: %s", resp.Message))
73+
To(gomega.Equal(runtimehooksv1.ResponseStatusSuccess), fmt.Sprintf("Message: %s", resp.Message))
7274

73-
if len(tt.ExpectedPatchMatchers) == 0 {
74-
g.Expect(resp.Items).To(gomega.BeEmpty())
75-
return
76-
}
7775
g.Expect(resp.Items).To(containPatches(&tt.RequestItem, tt.ExpectedPatchMatchers...))
7876

7977
if len(tt.UnexpectedPatchMatchers) > 0 {
@@ -111,6 +109,17 @@ func containPatches(
111109
requestItem *runtimehooksv1.GeneratePatchesRequestItem,
112110
jsonMatchers ...JSONPatchMatcher,
113111
) gomega.OmegaMatcher {
112+
if len(jsonMatchers) == 0 {
113+
return gomega.SatisfyAny(
114+
gomega.BeEmpty(),
115+
gomega.ContainElement(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
116+
"UID": gomega.Equal(requestItem.UID),
117+
"PatchType": gomega.Equal(runtimehooksv1.JSONPatchType),
118+
"Patch": gomega.Equal([]byte("[]")),
119+
})),
120+
)
121+
}
122+
114123
patchMatchers := make([]interface{}, 0, len(jsonMatchers))
115124
for patchIdx := range jsonMatchers {
116125
unexpectedPatch := jsonMatchers[patchIdx]

common/pkg/testutils/capitest/request/items.go

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@
44
package request
55

66
import (
7+
"encoding/json"
8+
"maps"
9+
710
corev1 "k8s.io/api/core/v1"
11+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
812
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
913
"k8s.io/apimachinery/pkg/runtime"
1014
"k8s.io/apimachinery/pkg/types"
1115
"k8s.io/apimachinery/pkg/util/uuid"
16+
"k8s.io/utils/ptr"
1217
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1318
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
1419
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
@@ -88,7 +93,9 @@ func NewKubeadmConfigTemplateRequest(
8893
}
8994

9095
type KubeadmControlPlaneTemplateRequestItemBuilder struct {
91-
files []bootstrapv1.File
96+
files []bootstrapv1.File
97+
version *string
98+
apiServerExtraArgs map[string]string
9299
}
93100

94101
func (b *KubeadmControlPlaneTemplateRequestItemBuilder) WithFiles(
@@ -98,43 +105,68 @@ func (b *KubeadmControlPlaneTemplateRequestItemBuilder) WithFiles(
98105
return b
99106
}
100107

108+
func (b *KubeadmControlPlaneTemplateRequestItemBuilder) WithKubernetesVersion(
109+
version string,
110+
) *KubeadmControlPlaneTemplateRequestItemBuilder {
111+
b.version = ptr.To(version)
112+
return b
113+
}
114+
115+
func (b *KubeadmControlPlaneTemplateRequestItemBuilder) WithAPIServerExtraArgs(
116+
extraArgs map[string]string,
117+
) *KubeadmControlPlaneTemplateRequestItemBuilder {
118+
b.apiServerExtraArgs = extraArgs
119+
return b
120+
}
121+
101122
func (b *KubeadmControlPlaneTemplateRequestItemBuilder) NewRequest(
102123
uid types.UID,
103124
) runtimehooksv1.GeneratePatchesRequestItem {
104-
return NewRequestItem(
105-
&controlplanev1.KubeadmControlPlaneTemplate{
106-
TypeMeta: metav1.TypeMeta{
107-
APIVersion: controlplanev1.GroupVersion.String(),
108-
Kind: "KubeadmControlPlaneTemplate",
109-
},
110-
ObjectMeta: metav1.ObjectMeta{
111-
Name: kubeadmControlPlaneTemplateRequestObjectName,
112-
Namespace: Namespace,
113-
},
114-
Spec: controlplanev1.KubeadmControlPlaneTemplateSpec{
115-
Template: controlplanev1.KubeadmControlPlaneTemplateResource{
116-
Spec: controlplanev1.KubeadmControlPlaneTemplateResourceSpec{
117-
KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
118-
InitConfiguration: &bootstrapv1.InitConfiguration{
119-
NodeRegistration: bootstrapv1.NodeRegistrationOptions{
120-
KubeletExtraArgs: map[string]string{
121-
"cloud-provider": "external",
122-
},
125+
cpTemplate := &controlplanev1.KubeadmControlPlaneTemplate{
126+
TypeMeta: metav1.TypeMeta{
127+
APIVersion: controlplanev1.GroupVersion.String(),
128+
Kind: "KubeadmControlPlaneTemplate",
129+
},
130+
ObjectMeta: metav1.ObjectMeta{
131+
Name: kubeadmControlPlaneTemplateRequestObjectName,
132+
Namespace: Namespace,
133+
},
134+
Spec: controlplanev1.KubeadmControlPlaneTemplateSpec{
135+
Template: controlplanev1.KubeadmControlPlaneTemplateResource{
136+
Spec: controlplanev1.KubeadmControlPlaneTemplateResourceSpec{
137+
KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
138+
InitConfiguration: &bootstrapv1.InitConfiguration{
139+
NodeRegistration: bootstrapv1.NodeRegistrationOptions{
140+
KubeletExtraArgs: map[string]string{
141+
"cloud-provider": "external",
123142
},
124143
},
125-
JoinConfiguration: &bootstrapv1.JoinConfiguration{
126-
NodeRegistration: bootstrapv1.NodeRegistrationOptions{
127-
KubeletExtraArgs: map[string]string{
128-
"cloud-provider": "external",
129-
},
144+
},
145+
JoinConfiguration: &bootstrapv1.JoinConfiguration{
146+
NodeRegistration: bootstrapv1.NodeRegistrationOptions{
147+
KubeletExtraArgs: map[string]string{
148+
"cloud-provider": "external",
130149
},
131150
},
132-
Files: b.files,
133151
},
152+
Files: b.files,
134153
},
135154
},
136155
},
137156
},
157+
}
158+
159+
if b.apiServerExtraArgs != nil {
160+
if cpTemplate.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration == nil {
161+
cpTemplate.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration = &bootstrapv1.ClusterConfiguration{}
162+
}
163+
cpTemplate.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration.APIServer.ExtraArgs = maps.Clone(
164+
b.apiServerExtraArgs,
165+
)
166+
}
167+
168+
requestItem := NewRequestItem(
169+
cpTemplate,
138170
&runtimehooksv1.HolderReference{
139171
APIVersion: clusterv1.GroupVersion.String(),
140172
Kind: "Cluster",
@@ -144,6 +176,22 @@ func (b *KubeadmControlPlaneTemplateRequestItemBuilder) NewRequest(
144176
},
145177
uid,
146178
)
179+
180+
if b.version != nil {
181+
marshaledBuiltin, _ := json.Marshal( //nolint:errchkjson // Marshalling is guaranteed to succeed.
182+
map[string]interface{}{
183+
"controlPlane": map[string]interface{}{
184+
"version": *b.version,
185+
},
186+
},
187+
)
188+
requestItem.Variables = append(requestItem.Variables, runtimehooksv1.Variable{
189+
Name: runtimehooksv1.BuiltinsName,
190+
Value: apiextensionsv1.JSON{Raw: marshaledBuiltin},
191+
})
192+
}
193+
194+
return requestItem
147195
}
148196

149197
func NewKubeadmControlPlaneTemplateRequestItem(

hack/examples/bases/aws/clusterclass/kustomization.yaml.tmpl

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ patches:
3434
- target:
3535
kind: KubeadmControlPlaneTemplate
3636
patch: |-
37-
- op: "replace"
38-
path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/apiServer/extraArgs/cloud-provider"
39-
value: "external"
4037
- op: "replace"
4138
path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/controllerManager/extraArgs/cloud-provider"
4239
value: "external"
@@ -52,6 +49,13 @@ patches:
5249
- op: "replace"
5350
path: "/spec/template/spec/joinConfiguration/nodeRegistration/kubeletExtraArgs/cloud-provider"
5451
value: "external"
52+
# Delete the API server cloud-provider flag from the template.
53+
# They will be added by the handler for k8s < 1.33.
54+
- target:
55+
kind: KubeadmControlPlaneTemplate
56+
patch: |-
57+
- op: "remove"
58+
path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/apiServer/extraArgs/cloud-provider"
5559

5660
# Delete the cluster-specific resources.
5761
- target:

hack/examples/bases/nutanix/clusterclass/kustomization.yaml.tmpl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ patches:
4646
- op: "remove"
4747
path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/apiServer/certSANs"
4848

49+
# TODO: Remove once https://github.com/nutanix-cloud-native/cluster-api-provider-nutanix/pull/519 is
50+
# merged and released.
51+
# Delete the API server cloud-provider flag from the template.
52+
# They will be added by the handler for k8s < 1.33.
53+
- target:
54+
kind: KubeadmControlPlaneTemplate
55+
patch: |-
56+
- op: "remove"
57+
path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/apiServer/extraArgs/cloud-provider"
58+
4959
# Template the kube-vip file.
5060
# The handler will set the variables if needed, or remove it.
5161
- target:
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2023 Nutanix. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package externalcloudprovider
4+
5+
import (
6+
"context"
7+
"fmt"
8+
9+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
10+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11+
bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
12+
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
13+
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
14+
ctrl "sigs.k8s.io/controller-runtime"
15+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
16+
17+
"github.com/blang/semver/v4"
18+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers/mutation"
19+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches"
20+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches/selectors"
21+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables"
22+
)
23+
24+
var (
25+
versionGreatOrEqualTo133Range = semver.MustParseRange(">=1.33.0-0")
26+
)
27+
28+
type externalCloudProviderPatchHandler struct{}
29+
30+
func NewControlPlanePatch() *externalCloudProviderPatchHandler {
31+
return &externalCloudProviderPatchHandler{}
32+
}
33+
34+
func (h *externalCloudProviderPatchHandler) Mutate(
35+
ctx context.Context,
36+
obj *unstructured.Unstructured,
37+
vars map[string]apiextensionsv1.JSON,
38+
holderRef runtimehooksv1.HolderReference,
39+
_ ctrlclient.ObjectKey,
40+
_ mutation.ClusterGetter,
41+
) error {
42+
log := ctrl.LoggerFrom(ctx).WithValues(
43+
"holderRef", holderRef,
44+
)
45+
46+
cpVersion, err := variables.Get[string](vars, runtimehooksv1.BuiltinsName, "controlPlane", "version")
47+
if err != nil {
48+
// This builtin variable is guaranteed to be provided for control plane component patch requests so if it is not
49+
// found then we can safely skip this patch for this request item.
50+
if variables.IsFieldNotFoundError(err) {
51+
log.V(5).
52+
WithValues("variables", vars).
53+
Info(
54+
"skipping external cloud-provider flag to control plane because CP Kubernetes version is not found",
55+
)
56+
return nil
57+
}
58+
59+
// This is a fatal error, we can't proceed without the control plane version.
60+
log.WithValues("variables", vars).Error(err, "failed to get control plane Kubernetes version from builtin variable")
61+
return fmt.Errorf("failed to get control plane Kubernetes version from builtin variable: %w", err)
62+
}
63+
64+
cpK8sVersion, err := semver.ParseTolerant(cpVersion)
65+
if err != nil {
66+
log.WithValues(
67+
"kubernetesVersion",
68+
cpVersion,
69+
).Error(err, "failed to parse control plane Kubernetes version")
70+
return fmt.Errorf("failed to parse control plane Kubernetes version: %w", err)
71+
}
72+
73+
if versionGreatOrEqualTo133Range(cpK8sVersion) {
74+
log.V(5).Info(
75+
"skipping external cloud-provider flag to control plane kubeadm config template because Kubernetes < 1.33.0",
76+
)
77+
return nil
78+
}
79+
80+
if err := patches.MutateIfApplicable(
81+
obj, vars, &holderRef, selectors.ControlPlane(), log,
82+
func(obj *controlplanev1.KubeadmControlPlaneTemplate) error {
83+
if obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration == nil {
84+
obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration = &bootstrapv1.ClusterConfiguration{}
85+
}
86+
if obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration.APIServer.ExtraArgs == nil {
87+
obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration.APIServer.ExtraArgs = make(map[string]string, 1)
88+
}
89+
obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration.APIServer.ExtraArgs["cloud-provider"] = "external"
90+
91+
return nil
92+
}); err != nil {
93+
return err
94+
}
95+
96+
return nil
97+
}

0 commit comments

Comments
 (0)