From 5dc9fb6cb845adfcaee95367bef067e3f857ad6b Mon Sep 17 00:00:00 2001 From: Dimitri Koshkin Date: Thu, 31 Oct 2024 14:27:05 -0700 Subject: [PATCH] feat: add new CoreDNSUpdateStrategy API --- api/v1alpha1/clusterconfig_types.go | 17 ++++++- .../caren.nutanix.com_awsclusterconfigs.yaml | 15 ++++-- ...aren.nutanix.com_dockerclusterconfigs.yaml | 15 ++++-- ...ren.nutanix.com_genericclusterconfigs.yaml | 16 +++++-- ...ren.nutanix.com_nutanixclusterconfigs.yaml | 15 ++++-- api/v1alpha1/zz_generated.deepcopy.go | 5 ++ docs/content/customization/generic/dns.md | 47 +++++++++++++++++++ .../generic/mutation/coredns/inject.go | 19 +++++--- .../generic/mutation/coredns/inject_test.go | 43 ++++++++++++++++- .../mutation/coredns/variables_test.go | 14 ++++++ 10 files changed, 180 insertions(+), 26 deletions(-) diff --git a/api/v1alpha1/clusterconfig_types.go b/api/v1alpha1/clusterconfig_types.go index 26d58f6a6..55cc5ee4f 100644 --- a/api/v1alpha1/clusterconfig_types.go +++ b/api/v1alpha1/clusterconfig_types.go @@ -12,6 +12,11 @@ import ( "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables" ) +const ( + CoreDNSUpdateStrategyAutomatic CoreDNSUpdateStrategy = "Automatic" + CoreDNSUpdateStrategyManual CoreDNSUpdateStrategy = "Manual" +) + var ( DefaultDockerCertSANs = []string{ "localhost", @@ -320,12 +325,20 @@ type DNS struct { CoreDNS *CoreDNS `json:"coreDNS,omitempty"` } +// +kubebuilder:validation:Optional +// +kubebuilder:validation:Enum=Manual;Automatic +type CoreDNSUpdateStrategy string + type CoreDNS struct { // Image required for overriding Kubernetes DNS image details. - // If the image version is not specified, - // the default version based on the cluster's Kubernetes version will be used. // +kubebuilder:validation:Optional Image *Image `json:"image,omitempty"` + + // UpdateStrategy defines the strategy for how the CoreDNS version will be updated. + // If not specified, the default value is Automatic, + // which sets the CoreDNS version based on the cluster's Kubernetes version. + // +kubebuilder:default=Automatic + UpdateStrategy *CoreDNSUpdateStrategy `json:"updateStrategy,omitempty"` } //nolint:gochecknoinits // Idiomatic to use init functions to register APIs with scheme. diff --git a/api/v1alpha1/crds/caren.nutanix.com_awsclusterconfigs.yaml b/api/v1alpha1/crds/caren.nutanix.com_awsclusterconfigs.yaml index 09a8c8473..3d13683cf 100644 --- a/api/v1alpha1/crds/caren.nutanix.com_awsclusterconfigs.yaml +++ b/api/v1alpha1/crds/caren.nutanix.com_awsclusterconfigs.yaml @@ -379,10 +379,7 @@ spec: description: CoreDNS defines the CoreDNS configuration for the cluster. properties: image: - description: |- - Image required for overriding Kubernetes DNS image details. - If the image version is not specified, - the default version based on the cluster's Kubernetes version will be used. + description: Image required for overriding Kubernetes DNS image details. properties: repository: description: Repository is used to override the image repository to pull from. @@ -393,6 +390,16 @@ spec: pattern: ^[\w][\w.-]{0,127}$ type: string type: object + updateStrategy: + default: Automatic + description: |- + UpdateStrategy defines the strategy for how the CoreDNS version will be updated. + If not specified, the default value is Automatic, + which sets the CoreDNS version based on the cluster's Kubernetes version. + enum: + - Manual + - Automatic + type: string type: object type: object encryptionAtRest: diff --git a/api/v1alpha1/crds/caren.nutanix.com_dockerclusterconfigs.yaml b/api/v1alpha1/crds/caren.nutanix.com_dockerclusterconfigs.yaml index b352732a3..9b9b95229 100644 --- a/api/v1alpha1/crds/caren.nutanix.com_dockerclusterconfigs.yaml +++ b/api/v1alpha1/crds/caren.nutanix.com_dockerclusterconfigs.yaml @@ -296,10 +296,7 @@ spec: description: CoreDNS defines the CoreDNS configuration for the cluster. properties: image: - description: |- - Image required for overriding Kubernetes DNS image details. - If the image version is not specified, - the default version based on the cluster's Kubernetes version will be used. + description: Image required for overriding Kubernetes DNS image details. properties: repository: description: Repository is used to override the image repository to pull from. @@ -310,6 +307,16 @@ spec: pattern: ^[\w][\w.-]{0,127}$ type: string type: object + updateStrategy: + default: Automatic + description: |- + UpdateStrategy defines the strategy for how the CoreDNS version will be updated. + If not specified, the default value is Automatic, + which sets the CoreDNS version based on the cluster's Kubernetes version. + enum: + - Manual + - Automatic + type: string type: object type: object docker: diff --git a/api/v1alpha1/crds/caren.nutanix.com_genericclusterconfigs.yaml b/api/v1alpha1/crds/caren.nutanix.com_genericclusterconfigs.yaml index 883bea1c8..f2e09af50 100644 --- a/api/v1alpha1/crds/caren.nutanix.com_genericclusterconfigs.yaml +++ b/api/v1alpha1/crds/caren.nutanix.com_genericclusterconfigs.yaml @@ -58,10 +58,8 @@ spec: cluster. properties: image: - description: |- - Image required for overriding Kubernetes DNS image details. - If the image version is not specified, - the default version based on the cluster's Kubernetes version will be used. + description: Image required for overriding Kubernetes DNS + image details. properties: repository: description: Repository is used to override the image @@ -74,6 +72,16 @@ spec: pattern: ^[\w][\w.-]{0,127}$ type: string type: object + updateStrategy: + default: Automatic + description: |- + UpdateStrategy defines the strategy for how the CoreDNS version will be updated. + If not specified, the default value is Automatic, + which sets the CoreDNS version based on the cluster's Kubernetes version. + enum: + - Manual + - Automatic + type: string type: object type: object encryptionAtRest: diff --git a/api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml b/api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml index 6f0a5ebe1..2de8ec83e 100644 --- a/api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml +++ b/api/v1alpha1/crds/caren.nutanix.com_nutanixclusterconfigs.yaml @@ -449,10 +449,7 @@ spec: description: CoreDNS defines the CoreDNS configuration for the cluster. properties: image: - description: |- - Image required for overriding Kubernetes DNS image details. - If the image version is not specified, - the default version based on the cluster's Kubernetes version will be used. + description: Image required for overriding Kubernetes DNS image details. properties: repository: description: Repository is used to override the image repository to pull from. @@ -463,6 +460,16 @@ spec: pattern: ^[\w][\w.-]{0,127}$ type: string type: object + updateStrategy: + default: Automatic + description: |- + UpdateStrategy defines the strategy for how the CoreDNS version will be updated. + If not specified, the default value is Automatic, + which sets the CoreDNS version based on the cluster's Kubernetes version. + enum: + - Manual + - Automatic + type: string type: object type: object encryptionAtRest: diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 31ea04516..ea1932307 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -587,6 +587,11 @@ func (in *CoreDNS) DeepCopyInto(out *CoreDNS) { *out = new(Image) **out = **in } + if in.UpdateStrategy != nil { + in, out := &in.UpdateStrategy, &out.UpdateStrategy + *out = new(CoreDNSUpdateStrategy) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CoreDNS. diff --git a/docs/content/customization/generic/dns.md b/docs/content/customization/generic/dns.md index fc3ce81fb..8eadf517e 100644 --- a/docs/content/customization/generic/dns.md +++ b/docs/content/customization/generic/dns.md @@ -34,6 +34,7 @@ spec: image: repository: my-registry.io/my-org/my-repo tag: "v1.11.3_custom.0" + updateStrategy: Manual ``` Applying this configuration will result in the following value being set: @@ -48,3 +49,49 @@ Applying this configuration will result in the following value being set: imageRepository: "my-registry.io/my-org/my-repo" imageTag: "v1.11.3_custom.0" ``` + +The CoreDNS version can also be updated automatically. To do this, set the `updateStrategy` to `Automatic`: + +```yaml +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + name: +spec: + topology: + variables: + - name: clusterConfig + value: + dns: + coreDNS: + updateStrategy: Automatic +``` + +Alternatively since the default value is `Automatic`, the following configuration is equivalent: + +```yaml +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + name: +spec: + topology: + variables: + - name: clusterConfig + value: + dns: + coreDNS: {} +``` + +Applying this configuration will result in the following value being set, +with the version of the CoreDNS image being set based on the cluster's Kubernetes version: + +- `KubeadmControlPlaneTemplate`: + + - ```yaml + spec: + kubeadmConfigSpec: + clusterConfiguration: + dns: + imageTag: "v1.11.3" + ``` diff --git a/pkg/handlers/generic/mutation/coredns/inject.go b/pkg/handlers/generic/mutation/coredns/inject.go index 993caba03..257dc8f28 100644 --- a/pkg/handlers/generic/mutation/coredns/inject.go +++ b/pkg/handlers/generic/mutation/coredns/inject.go @@ -9,6 +9,7 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/utils/ptr" bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" @@ -71,10 +72,11 @@ func (h *coreDNSPatchHandler) Mutate( h.variableFieldPath..., ) if err != nil { - if !variables.IsNotFoundError(err) { - return err + if variables.IsNotFoundError(err) { + log.V(5).Info("coreDNS variable not defined") + return nil } - log.V(5).Info("coreDNS variable not defined") + return err } log = log.WithValues( @@ -101,7 +103,7 @@ func (h *coreDNSPatchHandler) Mutate( log.WithValues( "patchedObjectKind", obj.GetObjectKind().GroupVersionKind().String(), "patchedObjectName", ctrlclient.ObjectKeyFromObject(obj), - ).Info("setting CoreDNS version") + ).Info("setting CoreDNS version if needed") if obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration == nil { obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration = &bootstrapv1.ClusterConfiguration{} @@ -119,9 +121,14 @@ func (h *coreDNSPatchHandler) Mutate( } } - // If the CoreDNS image tag is still not set, set the image tag to the default CoreDNS version based on the + // If the updateStrategy is set Automatic, set the image tag to the default CoreDNS version based on the // Kubernetes version. - if dns.ImageTag == "" { + // Default the strategy to Automatic if var is not nil. + strategy := ptr.Deref( + coreDNSVar.UpdateStrategy, + v1alpha1.CoreDNSUpdateStrategyAutomatic, + ) + if strategy == v1alpha1.CoreDNSUpdateStrategyAutomatic { defaultCoreDNSVersion, found := corednsversions.GetCoreDNSVersion( cluster.Spec.Topology.Version, ) diff --git a/pkg/handlers/generic/mutation/coredns/inject_test.go b/pkg/handlers/generic/mutation/coredns/inject_test.go index cbff3609c..0d0f08606 100644 --- a/pkg/handlers/generic/mutation/coredns/inject_test.go +++ b/pkg/handlers/generic/mutation/coredns/inject_test.go @@ -13,6 +13,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/utils/ptr" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" @@ -46,7 +47,34 @@ var _ = Describe("Generate CoreDNS patches", func() { testDefs := []testObj{ { patchTest: capitest.PatchTestDef{ - Name: "unset variable", + Name: "unset variable", + }, + cluster: clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: request.Namespace, + Labels: map[string]string{ + clusterv1.ProviderNameLabel: "nutanix", + }, + }, + Spec: clusterv1.ClusterSpec{ + Topology: &clusterv1.Topology{ + Version: "1.30.100", + }, + }, + }, + }, + { + patchTest: capitest.PatchTestDef{ + Name: "variable with defaults", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.ClusterConfigVariableName, + v1alpha1.CoreDNS{}, + v1alpha1.DNSVariableName, + VariableName, + ), + }, RequestItem: request.NewKubeadmControlPlaneTemplateRequestItem(""), ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ Operation: "add", @@ -85,6 +113,7 @@ var _ = Describe("Generate CoreDNS patches", func() { Repository: "my-registry.io/my-org/my-repo", Tag: "v1.11.3_custom.0", }, + UpdateStrategy: ptr.To(v1alpha1.CoreDNSUpdateStrategyManual), }, v1alpha1.DNSVariableName, VariableName, @@ -171,6 +200,7 @@ var _ = Describe("Generate CoreDNS patches", func() { Image: &v1alpha1.Image{ Tag: "v1.11.3_custom.0", }, + UpdateStrategy: ptr.To(v1alpha1.CoreDNSUpdateStrategyManual), }, v1alpha1.DNSVariableName, VariableName, @@ -205,7 +235,15 @@ var _ = Describe("Generate CoreDNS patches", func() { }, { patchTest: capitest.PatchTestDef{ - Name: "error if cannot find default CoreDNS version", + Name: "error if cannot find default CoreDNS version", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + v1alpha1.ClusterConfigVariableName, + v1alpha1.CoreDNS{}, + v1alpha1.DNSVariableName, + VariableName, + ), + }, RequestItem: request.NewKubeadmControlPlaneTemplateRequestItem(""), ExpectedFailure: true, }, @@ -235,6 +273,7 @@ var _ = Describe("Generate CoreDNS patches", func() { Repository: "my-registry.io/my-org/my-repo", Tag: "v1.11.3_custom.0", }, + UpdateStrategy: ptr.To(v1alpha1.CoreDNSUpdateStrategyManual), }, v1alpha1.DNSVariableName, VariableName, diff --git a/pkg/handlers/generic/mutation/coredns/variables_test.go b/pkg/handlers/generic/mutation/coredns/variables_test.go index 32c3e7138..804fcfe14 100644 --- a/pkg/handlers/generic/mutation/coredns/variables_test.go +++ b/pkg/handlers/generic/mutation/coredns/variables_test.go @@ -15,6 +15,10 @@ import ( nutanixclusterconfig "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/clusterconfig" ) +const ( + invalidUpdateStrategy = v1alpha1.CoreDNSUpdateStrategy("this:is:not:a:valid:update:strategy") +) + var testDefs = []capitest.VariableTestDef{{ Name: "unset", Vals: v1alpha1.GenericClusterConfigSpec{ @@ -78,6 +82,16 @@ var testDefs = []capitest.VariableTestDef{{ }, }, ExpectError: true, +}, { + Name: "set an invalid updateStrategy", + Vals: v1alpha1.GenericClusterConfigSpec{ + DNS: &v1alpha1.DNS{ + CoreDNS: &v1alpha1.CoreDNS{ + UpdateStrategy: ptr.To(invalidUpdateStrategy), + }, + }, + }, + ExpectError: true, }} func TestVariableValidation_AWS(t *testing.T) {