From f332c9e1e5318201ce3da880b84a605fd5cc8cee Mon Sep 17 00:00:00 2001 From: apedriza Date: Tue, 18 Nov 2025 15:00:37 +0100 Subject: [PATCH 1/2] Unify k0s version format Signed-off-by: apedriza --- .../v1beta1/k0smotroncluster_types.go | 34 +++++---- .../v1beta1/k0smotroncluster_types_test.go | 4 +- cmd/main.go | 2 +- config/webhook/manifests.yaml | 20 ++++++ .../k0smotron_controlplane_controller.go | 70 ++++++++++++++----- .../k0smotron_controlplane_controller_test.go | 36 ++++++++++ .../k0smotron_controlplane_webhook.go | 25 ++++++- .../k0smotron.io/k0smotroncluster_etcd.go | 2 +- .../k0smotroncluster_statefulset.go | 4 +- .../k0smotron.io/k0smotroncluster_webhook.go | 55 ++++++++------- 10 files changed, 188 insertions(+), 64 deletions(-) diff --git a/api/k0smotron.io/v1beta1/k0smotroncluster_types.go b/api/k0smotron.io/v1beta1/k0smotroncluster_types.go index 0e3620175..b4b1a7ff8 100644 --- a/api/k0smotron.io/v1beta1/k0smotroncluster_types.go +++ b/api/k0smotron.io/v1beta1/k0smotroncluster_types.go @@ -19,9 +19,10 @@ package v1beta1 import ( "crypto/md5" "fmt" + "strings" + "github.com/k0sproject/version" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - "strings" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -174,27 +175,34 @@ type Mount struct { } const ( - defaultK0SImage = "quay.io/k0sproject/k0s" - DefaultK0SVersion = "v1.27.9-k0s.0" - DefaultK0SSuffix = "k0s.0" - DefaultEtcdImage = "quay.io/k0sproject/etcd:v3.5.13" + defaultK0SRepository = "quay.io/k0sproject/k0s" + // DefaultK0SVersion is the default version used (kubernetes version + k0s version). + DefaultK0SVersion = "v1.27.9+k0s.0" + // DefaultK0SSuffix is the default k0s version used. + DefaultK0SSuffix = "k0s.0" + // DefaultEtcdImage is the default etcd image reference used. + DefaultEtcdImage = "quay.io/k0sproject/etcd:v3.5.13" ) -func (c *ClusterSpec) GetImage() string { - k0sVersion := c.Version - if k0sVersion == "" { - k0sVersion = DefaultK0SVersion +// GetK0sImageRef returns the k0s image reference. +func (c *ClusterSpec) GetK0sImageRef() string { + k0sTag := c.Version + if k0sTag == "" { + k0sTag = DefaultK0SVersion } - if !strings.Contains(k0sVersion, "-k0s.") { - k0sVersion = fmt.Sprintf("%s-%s", k0sVersion, DefaultK0SSuffix) + if !strings.Contains(k0sTag, "+k0s.") { + k0sTag = fmt.Sprintf("%s+%s", k0sTag, DefaultK0SSuffix) } + k0sImageRef := fmt.Sprintf("%s:%s", c.Image, k0sTag) if c.Image == "" { - return fmt.Sprintf("%s:%s", defaultK0SImage, k0sVersion) + k0sImageRef = fmt.Sprintf("%s:%s", defaultK0SRepository, k0sTag) } - return fmt.Sprintf("%s:%s", c.Image, k0sVersion) + // Mutate image reference to avoid "+" character in the k0s version tag which is not allowed in some + // registries like Docker Hub (https://github.com/distribution/reference/blob/main/reference.go) + return strings.ReplaceAll(k0sImageRef, "+k0s.", "-k0s.") } // ClusterStatus defines the observed state of K0smotronCluster diff --git a/api/k0smotron.io/v1beta1/k0smotroncluster_types_test.go b/api/k0smotron.io/v1beta1/k0smotroncluster_types_test.go index 032daf4f0..abf60e399 100644 --- a/api/k0smotron.io/v1beta1/k0smotroncluster_types_test.go +++ b/api/k0smotron.io/v1beta1/k0smotroncluster_types_test.go @@ -37,7 +37,7 @@ func TestClusterSpec_GetImage(t *testing.T) { { name: "Only version given with suffix", spec: &ClusterSpec{ - Version: "v1.29.4-k0s.0", + Version: "v1.29.4+k0s.0", }, want: "quay.io/k0sproject/k0s:v1.29.4-k0s.0", }, @@ -66,7 +66,7 @@ func TestClusterSpec_GetImage(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := tt.spec.GetImage(); got != tt.want { + if got := tt.spec.GetK0sImageRef(); got != tt.want { require.Equal(t, tt.want, got) } }) diff --git a/cmd/main.go b/cmd/main.go index 599915490..de8085c65 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -327,7 +327,7 @@ func main() { os.Exit(1) } - if err = (&controlplane.K0smotronControlPlaneValidator{}).SetupK0smotronControlPlaneWebhookWithManager(mgr); err != nil { + if err = controlplane.SetupK0smotronControlPlaneWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create validation webhook", "webhook", "K0smotronControlPlaneValidator") os.Exit(1) } diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 96086fadf..bedb79443 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -24,6 +24,26 @@ webhooks: resources: - clusters sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-controlplane-cluster-x-k8s-io-v1beta1-k0smotroncontrolplane + failurePolicy: Fail + name: mutate-k0smotroncontrolplane-v1beta1.k0smotron.io + rules: + - apiGroups: + - controlplane.cluster.x-k8s.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - k0smotroncontrolplanes + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration diff --git a/internal/controller/controlplane/k0smotron_controlplane_controller.go b/internal/controller/controlplane/k0smotron_controlplane_controller.go index 6fd8ec5cc..b55dc5c52 100644 --- a/internal/controller/controlplane/k0smotron_controlplane_controller.go +++ b/internal/controller/controlplane/k0smotron_controlplane_controller.go @@ -417,16 +417,25 @@ func ensureCertificates(ctx context.Context, cluster *clusterv1.Cluster, kcp *cp } // FormatStatusVersion formats the status version to match the spec version format. -// If spec.version doesn't contain "-k0s." suffix, it removes the suffix from status.version as well. +// If spec.version doesn't contain "-k0s." or "+k0s." suffix, it removes the suffix from status.version as well. +// TODO: Remove support for "-k0s." suffix in future versions. func FormatStatusVersion(specVersion, statusVersion string) string { - specHasK0sSuffix := strings.Contains(specVersion, "-k0s.") - - // Adjust status.version to match the format of spec.version - if !specHasK0sSuffix && strings.Contains(statusVersion, "-k0s.") { - // If spec.version doesn't have the -k0s. suffix, remove it from status.version as well - parts := strings.Split(statusVersion, "-k0s.") - if len(parts) > 0 { - return parts[0] + specHasK0sSuffix := strings.Contains(specVersion, "-k0s.") || strings.Contains(specVersion, "+k0s.") + + if !specHasK0sSuffix { + if strings.Contains(statusVersion, "-k0s.") { + // If spec.version doesn't have the -k0s. suffix, remove it from status.version as well + parts := strings.Split(statusVersion, "-k0s.") + if len(parts) > 0 { + return parts[0] + } + } + if strings.Contains(statusVersion, "+k0s.") { + // If spec.version doesn't have the +k0s. suffix, remove it from status.version as well + parts := strings.Split(statusVersion, "+k0s.") + if len(parts) > 0 { + return parts[0] + } } } @@ -462,8 +471,8 @@ func (c *K0smotronController) computeStatus(ctx context.Context, cluster *cluste var updatedReplicas, readyReplicas, unavailableReplicas int desiredVersionStr := kcp.Spec.Version - if !strings.Contains(desiredVersionStr, "-") { - desiredVersionStr = fmt.Sprintf("%s-%s", desiredVersionStr, kapi.DefaultK0SSuffix) + if !strings.Contains(desiredVersionStr, "-") && !strings.Contains(desiredVersionStr, "+") { + desiredVersionStr = fmt.Sprintf("%s+%s", desiredVersionStr, kapi.DefaultK0SSuffix) } desiredVersion, err := version.NewVersion(desiredVersionStr) if err != nil { @@ -488,7 +497,12 @@ func (c *K0smotronController) computeStatus(ctx context.Context, cluster *cluste continue } - currentVersion, err := scope.getComparableK0sVersionRunningInPod(ctx, &pod) + currentVersion, err := scope.getK0sVersionRunningInPod(ctx, &pod) + if err != nil { + return err + } + // Align version format in spec to the current version format for comparison. DO NOT modify version format in spec. + currentVersion, err = alignToSpecVersionFormat(desiredVersion, currentVersion) if err != nil { return err } @@ -527,6 +541,32 @@ func (c *K0smotronController) computeStatus(ctx context.Context, cluster *cluste return nil } +// alignToSpecVersionFormat ensures that the currentVersion format matches the desiredVersion format. +// TODO: Once the "-k0s." suffix is fully deprecated, this function can be removed. +func alignToSpecVersionFormat(specVersion, currentVersion *version.Version) (*version.Version, error) { + specVersionUsesDefaultK0SSuffix := strings.Contains(specVersion.String(), "+") + currentVersionUsesDefaultK0SSuffix := strings.Contains(currentVersion.String(), "+") + + isFormatAligned := (specVersionUsesDefaultK0SSuffix && currentVersionUsesDefaultK0SSuffix) || + (!specVersionUsesDefaultK0SSuffix && !currentVersionUsesDefaultK0SSuffix) + + if isFormatAligned { + return currentVersion, nil + } + + currentVersionStr := currentVersion.String() + if !specVersionUsesDefaultK0SSuffix { + // Spec version format is like "vX.Y.Z-k0s.0". Convert currentVersion to match it. + currentVersionAlignedStr := strings.Replace(currentVersionStr, "+k0s.", "-k0s.", 1) + return version.NewVersion(currentVersionAlignedStr) + } + + // Current version format is like "vX.Y.Z-k0s.0". Convert currentVersion to match it. + currentVersionAlignedStr := strings.Replace(currentVersionStr, "-k0s.", "+k0s.", 1) + return version.NewVersion(currentVersionAlignedStr) + +} + // computeAvailability checks if the control plane is ready by connecting to the API server // and checking if the control plane is initialized func (c *K0smotronController) computeAvailability(ctx context.Context, cluster *clusterv1.Cluster, kcp *cpv1beta1.K0smotronControlPlane) { @@ -574,16 +614,12 @@ func (c *K0smotronController) computeAvailability(ctx context.Context, cluster * }) } -func (scope *kmcScope) getComparableK0sVersionRunningInPod(ctx context.Context, pod *corev1.Pod) (*version.Version, error) { +func (scope *kmcScope) getK0sVersionRunningInPod(ctx context.Context, pod *corev1.Pod) (*version.Version, error) { currentVersionOutput, err := exec.PodExecCmdOutput(ctx, scope.clientSet, scope.restConfig, pod.GetName(), pod.GetNamespace(), "k0s version") if err != nil { return nil, err } currentVersionStr, _ := strings.CutSuffix(currentVersionOutput, "\n") - // In order to compare the version reported by the 'k0s version' command executed in the pod running - // the controlplane with the version declared in K0smotronControlPlane.spec this transformation is - // necessary to match their format. - currentVersionStr = strings.Replace(currentVersionStr, "+", "-", 1) return version.NewVersion(currentVersionStr) } diff --git a/internal/controller/controlplane/k0smotron_controlplane_controller_test.go b/internal/controller/controlplane/k0smotron_controlplane_controller_test.go index 43e17f073..3ca05acc4 100644 --- a/internal/controller/controlplane/k0smotron_controlplane_controller_test.go +++ b/internal/controller/controlplane/k0smotron_controlplane_controller_test.go @@ -4,6 +4,7 @@ import ( "testing" kapi "github.com/k0sproject/k0smotron/api/k0smotron.io/v1beta1" + "github.com/k0sproject/version" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -247,3 +248,38 @@ func TestIsClusterSpecSynced(t *testing.T) { }) } } + +func Test_alignToSpecVersionFormat(t *testing.T) { + tests := []struct { + name string + specVersion *version.Version + currentVersion *version.Version + want *version.Version + }{ + { + name: "both versions have same format", + specVersion: version.MustParse("v1.33.1-k0s.0"), + currentVersion: version.MustParse("v1.33.1-k0s.1"), + want: version.MustParse("v1.33.1-k0s.1"), + }, + { + name: "versions does not have same format: spec with +k0s, current with -k0s", + specVersion: version.MustParse("v1.33.1+k0s.0"), + currentVersion: version.MustParse("v1.33.1-k0s.1"), + want: version.MustParse("v1.33.1+k0s.1"), + }, + { + name: "versions does not have same format: spec with -k0s, current with +k0s", + specVersion: version.MustParse("v1.33.1-k0s.0"), + currentVersion: version.MustParse("v1.33.1+k0s.1"), + want: version.MustParse("v1.33.1-k0s.1"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := alignToSpecVersionFormat(tt.specVersion, tt.currentVersion) + require.NoError(t, err) + require.True(t, tt.want.Equal(got), "alignToSpecVersionFormat() = %v, want %v", got, tt.want) + }) + } +} diff --git a/internal/controller/controlplane/k0smotron_controlplane_webhook.go b/internal/controller/controlplane/k0smotron_controlplane_webhook.go index 775a76b41..439bb11bf 100644 --- a/internal/controller/controlplane/k0smotron_controlplane_webhook.go +++ b/internal/controller/controlplane/k0smotron_controlplane_webhook.go @@ -19,6 +19,7 @@ package controlplane import ( "context" "fmt" + k0smotronio "github.com/k0sproject/k0smotron/internal/controller/k0smotron.io" "github.com/k0sproject/version" "k8s.io/apimachinery/pkg/runtime" @@ -30,6 +31,7 @@ import ( ) // +kubebuilder:webhook:path=/validate-controlplane-cluster-x-k8s-io-v1beta1-k0smotroncontrolplane,mutating=false,failurePolicy=fail,sideEffects=None,groups=controlplane.cluster.x-k8s.io,resources=k0smotroncontrolplanes,verbs=create;update,versions=v1beta1,name=validate-k0smotroncontrolplane-v1beta1.k0smotron.io,admissionReviewVersions=v1 +// +kubebuilder:webhook:path=/mutate-controlplane-cluster-x-k8s-io-v1beta1-k0smotroncontrolplane,mutating=true,failurePolicy=fail,sideEffects=None,groups=controlplane.cluster.x-k8s.io,resources=k0smotroncontrolplanes,verbs=create;update,versions=v1beta1,name=mutate-k0smotroncontrolplane-v1beta1.k0smotron.io,admissionReviewVersions=v1 // K0smotronControlPlaneValidator struct is responsible for validating the K0smotronControlPlane resource when it is created, updated, or deleted. // @@ -39,7 +41,25 @@ type K0smotronControlPlaneValidator struct { cv k0smotronio.ClusterValidator } +// K0smotronControlPlaneDefaulter struct is responsible for defaulting the K0smotronControlPlane resource when it is created or updated. +// +// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, +// as this struct is used only for temporary operations and does not need to be deeply copied. +type K0smotronControlPlaneDefaulter struct { + cv k0smotronio.ClusterDefaulter +} + var _ webhook.CustomValidator = &K0smotronControlPlaneValidator{} +var _ webhook.CustomDefaulter = &K0smotronControlPlaneDefaulter{} + +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type K0smotronControlPlane. +func (d *K0smotronControlPlaneDefaulter) Default(_ context.Context, obj runtime.Object) error { + kcp, ok := obj.(*v1beta1.K0smotronControlPlane) + if !ok { + return fmt.Errorf("expected a K0smotronControlPlane object but got %T", obj) + } + return d.cv.DefaultClusterSpec(&kcp.Spec) +} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type K0smotronControlPlane. func (v *K0smotronControlPlaneValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { @@ -108,9 +128,10 @@ func (v *K0smotronControlPlaneValidator) ValidateDelete(_ context.Context, _ run } // SetupK0smotronControlPlaneWebhookWithManager registers the webhook for K0smotronControlPlane in the manager. -func (v *K0smotronControlPlaneValidator) SetupK0smotronControlPlaneWebhookWithManager(mgr ctrl.Manager) error { +func SetupK0smotronControlPlaneWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(&v1beta1.K0smotronControlPlane{}). - WithValidator(v). + WithValidator(&K0smotronControlPlaneValidator{}). + WithDefaulter(&K0smotronControlPlaneDefaulter{}). Complete() } diff --git a/internal/controller/k0smotron.io/k0smotroncluster_etcd.go b/internal/controller/k0smotron.io/k0smotroncluster_etcd.go index 714a01884..1c9c407c5 100644 --- a/internal/controller/k0smotron.io/k0smotroncluster_etcd.go +++ b/internal/controller/k0smotron.io/k0smotroncluster_etcd.go @@ -406,7 +406,7 @@ func initialCluster(kmc *km.Cluster, replicas int32) string { } func generateEtcdInitContainers(kmc *km.Cluster, existingSts *apps.StatefulSet) []v1.Container { - checkImage := kmc.Spec.GetImage() + checkImage := kmc.Spec.GetK0sImageRef() if existingSts != nil { for _, c := range existingSts.Spec.Template.Spec.InitContainers { if c.Name == "dns-check" { diff --git a/internal/controller/k0smotron.io/k0smotroncluster_statefulset.go b/internal/controller/k0smotron.io/k0smotroncluster_statefulset.go index 59f188c5b..15d27d75d 100644 --- a/internal/controller/k0smotron.io/k0smotroncluster_statefulset.go +++ b/internal/controller/k0smotron.io/k0smotroncluster_statefulset.go @@ -118,7 +118,7 @@ func (scope *kmcScope) generateStatefulSet(kmc *km.Cluster) (apps.StatefulSet, e }}, Containers: []v1.Container{{ Name: "controller", - Image: kmc.Spec.GetImage(), + Image: kmc.Spec.GetK0sImageRef(), ImagePullPolicy: v1.PullIfNotPresent, Args: []string{"/bin/sh", "-c", "/k0smotron-entrypoint.sh"}, Ports: []v1.ContainerPort{ @@ -442,7 +442,7 @@ func mountSecrets(kmc *km.Cluster, sfs *apps.StatefulSet) { // Otherwise k0s will trip over the permissions and RO mounts sfs.Spec.Template.Spec.InitContainers = append(sfs.Spec.Template.Spec.InitContainers, v1.Container{ Name: "certs-init", - Image: kmc.Spec.GetImage(), + Image: kmc.Spec.GetK0sImageRef(), Command: []string{ "sh", "-c", diff --git a/internal/controller/k0smotron.io/k0smotroncluster_webhook.go b/internal/controller/k0smotron.io/k0smotroncluster_webhook.go index 571b9a1dd..c3bca60ba 100644 --- a/internal/controller/k0smotron.io/k0smotroncluster_webhook.go +++ b/internal/controller/k0smotron.io/k0smotroncluster_webhook.go @@ -56,8 +56,6 @@ func (c ClusterValidator) ValidateDelete(_ context.Context, _ runtime.Object) (w // ValidateClusterSpec validates the ClusterSpec and returns any warnings or errors. func (c ClusterValidator) ValidateClusterSpec(kcs *km.ClusterSpec) (warnings admission.Warnings, err error) { - warnings = c.validateVersionSuffix(kcs.Version) - if kcs.Ingress != nil { warn, err := kcs.Ingress.Validate(kcs.Version) warnings = append(warnings, warn...) @@ -73,43 +71,48 @@ func (c ClusterValidator) ValidateClusterSpec(kcs *km.ClusterSpec) (warnings adm return warnings, nil } -// validateVersionSuffix checks if the version has a k0s suffix and returns a warning if it doesn't -func (c ClusterValidator) validateVersionSuffix(version string) admission.Warnings { - warnings := admission.Warnings{} - if version != "" && !strings.Contains(version, "-k0s.") { - warnings = append(warnings, fmt.Sprintf("The specified version '%s' requires a k0s suffix (-k0s.). Using '%s-k0s.0' instead.", version, version)) - } - - return warnings -} - -func (c *ClusterDefaulter) Default(_ context.Context, obj runtime.Object) error { +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type Cluster. +func (c ClusterDefaulter) Default(_ context.Context, obj runtime.Object) error { kmc, ok := obj.(*km.Cluster) if !ok { return fmt.Errorf("expected a Cluster object but got %T", obj) } - if kmc.Spec.Replicas == 0 { - kmc.Spec.Replicas = 1 + return c.DefaultClusterSpec(&kmc.Spec) +} + +// DefaultClusterSpec sets default values for the ClusterSpec. +func (c *ClusterDefaulter) DefaultClusterSpec(kmcSpec *km.ClusterSpec) error { + if kmcSpec.Replicas == 0 { + kmcSpec.Replicas = 1 } - if kmc.Spec.Version == "" { - kmc.Spec.Version = km.DefaultK0SVersion + if kmcSpec.Version == "" { + kmcSpec.Version = km.DefaultK0SVersion + } else { + // Ensure we store the version with "+k0s.". That way we can standardize the k0s version format. + // Needed sanitization will be done in runtime when building image references and not required + // by the user. + kmcSpec.Version = strings.ReplaceAll(kmcSpec.Version, "-k0s.", "+k0s.") + // Always append the default k0s version if not present. + if !strings.Contains(kmcSpec.Version, "+k0s.") { + kmcSpec.Version = fmt.Sprintf("%s+%s", kmcSpec.Version, km.DefaultK0SSuffix) + } } - if kmc.Spec.Service.Type == "" { - kmc.Spec.Service.Type = corev1.ServiceTypeClusterIP - kmc.Spec.Service.APIPort = 30443 - kmc.Spec.Service.KonnectivityPort = 30132 + if kmcSpec.Service.Type == "" { + kmcSpec.Service.Type = corev1.ServiceTypeClusterIP + kmcSpec.Service.APIPort = 30443 + kmcSpec.Service.KonnectivityPort = 30132 } - if kmc.Spec.Etcd.Image == "" { - kmc.Spec.Etcd.Image = km.DefaultEtcdImage + if kmcSpec.Etcd.Image == "" { + kmcSpec.Etcd.Image = km.DefaultEtcdImage } - if kmc.Spec.Ingress != nil { - if kmc.Spec.Ingress.Deploy == nil { - kmc.Spec.Ingress.Deploy = ptr.To(true) + if kmcSpec.Ingress != nil { + if kmcSpec.Ingress.Deploy == nil { + kmcSpec.Ingress.Deploy = ptr.To(true) } } From 7aa8a3795beda6a90096732eeeca1eaf142c48bd Mon Sep 17 00:00:00 2001 From: apedriza Date: Wed, 19 Nov 2025 10:23:25 +0100 Subject: [PATCH 2/2] Update old references Signed-off-by: apedriza --- .../v1beta1/k0smotroncluster_types.go | 3 +- .../capi/capi-controlplane-hetzner.yaml | 2 +- .../cluster-with-machinedeployment.yaml | 2 +- .../samples/capi/docker/docker-cluster.yaml | 2 +- docs/capi-aws.md | 2 +- docs/capi-controlplane.md | 2 +- docs/capi-docker.md | 2 +- docs/capi-hetzner.md | 2 +- docs/capi-kubevirt.md | 2 +- docs/capi-remote.md | 2 +- docs/capi-vsphere.md | 28 +++++++++---------- docs/configuration.md | 2 +- docs/hcp-autoscaling.md | 2 +- docs/ingress-support.md | 4 +-- docs/update/hcp-autopilot.md | 2 +- docs/update/update-cluster-pod.md | 6 ++-- docs/update/update-standalone.md | 4 +-- .../main/bases/cluster-with-hcp.yaml | 2 +- .../cluster-with-ingress.yaml | 2 +- .../hcp-with-external-cluster-patch.yaml | 2 +- ...capi_docker_clusterclass_k0smotron_test.go | 2 +- inttest/capi-docker/capi_docker_test.go | 2 +- .../capi_remote_machine_job_provision_test.go | 2 +- .../capi_remote_machine_test.go | 2 +- .../config-update-hcp/config_update_test.go | 2 +- .../ha_controller_etcd_test.go | 4 +-- .../ha_controller_secret_test.go | 2 +- inttest/hostpath/hostpath_test.go | 2 +- inttest/jointoken/jointoken_test.go | 2 +- inttest/monitoring/monitoring_test.go | 2 +- inttest/pvc/pvc_test.go | 2 +- inttest/scaling-etcd/scaling_etcd_test.go | 2 +- templates/cluster-template-hcp.yaml | 2 +- 33 files changed, 52 insertions(+), 51 deletions(-) diff --git a/api/k0smotron.io/v1beta1/k0smotroncluster_types.go b/api/k0smotron.io/v1beta1/k0smotroncluster_types.go index b4b1a7ff8..ce57674a5 100644 --- a/api/k0smotron.io/v1beta1/k0smotroncluster_types.go +++ b/api/k0smotron.io/v1beta1/k0smotroncluster_types.go @@ -191,7 +191,8 @@ func (c *ClusterSpec) GetK0sImageRef() string { k0sTag = DefaultK0SVersion } - if !strings.Contains(k0sTag, "+k0s.") { + // Old references might contain "-k0s." suffix instead of "+k0s." so we check only for the presence of "k0s." + if !strings.Contains(k0sTag, "k0s.") { k0sTag = fmt.Sprintf("%s+%s", k0sTag, DefaultK0SSuffix) } diff --git a/config/samples/capi/capi-controlplane-hetzner.yaml b/config/samples/capi/capi-controlplane-hetzner.yaml index 082c48f47..3f9896471 100644 --- a/config/samples/capi/capi-controlplane-hetzner.yaml +++ b/config/samples/capi/capi-controlplane-hetzner.yaml @@ -24,7 +24,7 @@ kind: K0smotronControlPlane # This would somehow map to k0smotron metadata: name: cp-test spec: - version: v1.27.2-k0s.0 + version: v1.27.2+k0s.0 persistence: type: emptyDir service: diff --git a/config/samples/capi/docker/cluster-with-machinedeployment.yaml b/config/samples/capi/docker/cluster-with-machinedeployment.yaml index b3bced71a..fe896c715 100644 --- a/config/samples/capi/docker/cluster-with-machinedeployment.yaml +++ b/config/samples/capi/docker/cluster-with-machinedeployment.yaml @@ -26,7 +26,7 @@ kind: K0smotronControlPlane metadata: name: docker-md-test spec: - version: v1.27.2-k0s.0 + version: v1.27.2+k0s.0 persistence: type: emptyDir service: diff --git a/config/samples/capi/docker/docker-cluster.yaml b/config/samples/capi/docker/docker-cluster.yaml index 9cd2f84b3..928a4ad03 100644 --- a/config/samples/capi/docker/docker-cluster.yaml +++ b/config/samples/capi/docker/docker-cluster.yaml @@ -26,7 +26,7 @@ kind: K0smotronControlPlane metadata: name: docker-test spec: - version: v1.27.2-k0s.0 + version: v1.27.2+k0s.0 persistence: type: emptyDir service: diff --git a/docs/capi-aws.md b/docs/capi-aws.md index a872140b0..75124abb4 100644 --- a/docs/capi-aws.md +++ b/docs/capi-aws.md @@ -50,7 +50,7 @@ kind: K0smotronControlPlane # This is the config for the controlplane metadata: name: k0s-aws-test-cp spec: - version: v1.27.2-k0s.0 + version: v1.27.2+k0s.0 persistence: type: emptyDir service: diff --git a/docs/capi-controlplane.md b/docs/capi-controlplane.md index 0ccb2695a..afe500f43 100644 --- a/docs/capi-controlplane.md +++ b/docs/capi-controlplane.md @@ -30,7 +30,7 @@ kind: K0smotronControlPlane metadata: name: cp-test spec: - version: v1.27.2-k0s.0 + version: v1.27.2+k0s.0 persistence: type: emptyDir service: diff --git a/docs/capi-docker.md b/docs/capi-docker.md index 375c32156..c8ed418c0 100644 --- a/docs/capi-docker.md +++ b/docs/capi-docker.md @@ -54,7 +54,7 @@ metadata: name: docker-test-cp namespace: default spec: - version: v1.27.2-k0s.0 + version: v1.27.2+k0s.0 persistence: type: emptyDir service: diff --git a/docs/capi-hetzner.md b/docs/capi-hetzner.md index bfa0dfa76..816638578 100644 --- a/docs/capi-hetzner.md +++ b/docs/capi-hetzner.md @@ -51,7 +51,7 @@ kind: K0smotronControlPlane # This is the config for the controlplane metadata: name: hetzner-test-cp spec: - version: v1.27.2-k0s.0 + version: v1.27.2+k0s.0 persistence: type: emptyDir service: diff --git a/docs/capi-kubevirt.md b/docs/capi-kubevirt.md index 56682b8ad..d701df5e1 100644 --- a/docs/capi-kubevirt.md +++ b/docs/capi-kubevirt.md @@ -64,7 +64,7 @@ kind: K0smotronControlPlane # This is the config for the controlplane metadata: name: k0s-test-cp spec: - version: v1.27.4-k0s.0 + version: v1.27.4+k0s.0 persistence: type: emptyDir service: diff --git a/docs/capi-remote.md b/docs/capi-remote.md index 1656e3fa8..fb187b113 100644 --- a/docs/capi-remote.md +++ b/docs/capi-remote.md @@ -41,7 +41,7 @@ metadata: name: remote-test namespace: default spec: - version: v1.27.2-k0s.0 + version: v1.27.2+k0s.0 persistence: type: emptyDir service: diff --git a/docs/capi-vsphere.md b/docs/capi-vsphere.md index ab06d0d73..26651a6b9 100644 --- a/docs/capi-vsphere.md +++ b/docs/capi-vsphere.md @@ -5,19 +5,19 @@ This example demonstrates how k0smotron can be used with CAPV (Cluster API Provi **Table of Contents** - [Cluster API - VMware](#cluster-api---vmware) - * [Setting the scene](#setting-the-scene) - * [Preparations](#preparations) - + [Configure clusterctl on the local machine](#configure-clusterctl-on-the-local-machine) - + [Deploy Cluster API in the management cluster](#deploy-cluster-api-in-the-management-cluster) - + [(Optional) IPAM IP pool creation](#optional-ipam-ip-pool-creation) - * [(Optional) MetalLB as Load Balancer solution in the management cluster](#optional-metallb-as-load-balancer-solution-in-the-management-cluster) - * [Operating child clusters](#operating-child-clusters) - + [Generate a child cluster definition using the template](#generate-a-child-cluster-definition-using-the-template) - - [Control plane in Pods](#control-plane-in-pods) - - [Control plane in VMs](#control-plane-in-vms) - + [Deploy the child clusters](#deploy-the-child-clusters) - + [Observe the child cluster objects](#observe-the-child-cluster-objects) - + [Deleting the child cluster](#deleting-the-child-cluster) + - [Setting the scene](#setting-the-scene) + - [Preparations](#preparations) + - [Configure clusterctl on the local machine](#configure-clusterctl-on-the-local-machine) + - [Deploy Cluster API in the management cluster](#deploy-cluster-api-in-the-management-cluster) + - [(Optional) IPAM IP pool creation](#optional-ipam-ip-pool-creation) + - [(Optional) MetalLB as Load Balancer solution in the management cluster](#optional-metallb-as-load-balancer-solution-in-the-management-cluster) + - [Operating child clusters](#operating-child-clusters) + - [Generate a child cluster definition using the template](#generate-a-child-cluster-definition-using-the-template) + - [Control plane in Pods:](#control-plane-in-pods) + - [Control plane in VMs:](#control-plane-in-vms) + - [Deploy the child clusters](#deploy-the-child-clusters) + - [Observe the child cluster objects](#observe-the-child-cluster-objects) + - [Deleting the child cluster](#deleting-the-child-cluster) ## Setting the scene @@ -63,7 +63,7 @@ VSPHERE_STORAGE_POLICY: "" # This is the vSph # Keep this close to the minimum Kubernetes version of the cluster being created. CSI_INSECURE: "1" K0S_VERSION: "v1.29.1+k0s.1" -K0S_CP_VERSION: "v1.29.1-k0s.1" +K0S_CP_VERSION: "v1.29.1+k0s.1" NODE_IPAM_POOL_NAME: "ipam-ip-pool" NODE_IPAM_POOL_API_GROUP: "ipam.cluster.x-k8s.io" NODE_IPAM_POOL_KIND: "InClusterIPPool" diff --git a/docs/configuration.md b/docs/configuration.md index 78ba2f74e..c96206549 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -10,7 +10,7 @@ metadata: spec: replicas: 1 image: quay.io/k0sproject/k0s - version: v1.27.1-k0s.0 + version: v1.27.1+k0s.0 service: type: NodePort apiPort: 30443 diff --git a/docs/hcp-autoscaling.md b/docs/hcp-autoscaling.md index c926e1a84..902378ff4 100644 --- a/docs/hcp-autoscaling.md +++ b/docs/hcp-autoscaling.md @@ -38,7 +38,7 @@ metadata: namespace: default spec: replicas: 1 - version: "v1.31.5-k0s.0" + version: "v1.31.5+k0s.0" service: type: NodePort resources: diff --git a/docs/ingress-support.md b/docs/ingress-support.md index 4b3050d1d..59c1bc565 100644 --- a/docs/ingress-support.md +++ b/docs/ingress-support.md @@ -59,7 +59,7 @@ metadata: name: my-cluster-cp namespace: default spec: - version: v1.34.0-k0s.0 + version: v1.34.0+k0s.0 ingress: apiHost: kube-api.example.com konnectivityHost: konnectivity.example.com @@ -80,7 +80,7 @@ metadata: name: my-cluster namespace: default spec: - version: v1.34.0-k0s.0 + version: v1.34.0+k0s.0 ingress: apiHost: kube-api.example.com konnectivityHost: konnectivity.example.com diff --git a/docs/update/hcp-autopilot.md b/docs/update/hcp-autopilot.md index dddd15921..2800db474 100644 --- a/docs/update/hcp-autopilot.md +++ b/docs/update/hcp-autopilot.md @@ -20,7 +20,7 @@ metadata: spec: replicas: 1 k0sImage: quay.io/k0sproject/k0s - version: v1.33.1-k0s.0 # new k0s version + version: v1.33.1+k0s.0 # new k0s version ``` !!! warning diff --git a/docs/update/update-cluster-pod.md b/docs/update/update-cluster-pod.md index 39f467b71..97b8663c1 100644 --- a/docs/update/update-cluster-pod.md +++ b/docs/update/update-cluster-pod.md @@ -34,7 +34,7 @@ the k0s version and machine names in the YAML configuration file: metadata: name: docker-test-cp spec: - version: v1.27.2-k0s.0 + version: v1.27.2+k0s.0 ``` 2. Make sure that the [persistence](../resource-reference/k0smotron.io-v1beta1.md#clusterspecpersistence) is configured to prevent data loss. For example: @@ -46,7 +46,7 @@ to prevent data loss. For example: metadata: name: docker-test-cp spec: - version: v1.27.2-k0s.0 + version: v1.27.2+k0s.0 persistence: type: hostPath hostPath: "/tmp/kmc-test" # k0smotron will mount a basic hostPath volume to avoid data loss. @@ -64,7 +64,7 @@ to prevent data loss. For example: metadata: name: cp-test spec: - version: v1.28.7-k0s.0 # new k0s version + version: v1.28.7+k0s.0 # new k0s version ``` 4. In the same configuration, replace the names of machines running the old k0smotron version diff --git a/docs/update/update-standalone.md b/docs/update/update-standalone.md index 881dca99b..52a6f4512 100644 --- a/docs/update/update-standalone.md +++ b/docs/update/update-standalone.md @@ -13,7 +13,7 @@ in the YAML configuration file: spec: replicas: 1 k0sImage: quay.io/k0sproject/k0s - version: v1.27.1-k0s.0 + version: v1.27.1+k0s.0 ``` 2. Change all the k0s versions to the target one. For example: @@ -26,7 +26,7 @@ in the YAML configuration file: spec: replicas: 1 k0sImage: quay.io/k0sproject/k0s - version: v1.28.7-k0s.0 # new k0s version + version: v1.28.7+k0s.0 # new k0s version ``` 3. Update the resources: diff --git a/e2e/data/infrastructure-docker/main/bases/cluster-with-hcp.yaml b/e2e/data/infrastructure-docker/main/bases/cluster-with-hcp.yaml index aa0e2f90a..5402cd2ce 100644 --- a/e2e/data/infrastructure-docker/main/bases/cluster-with-hcp.yaml +++ b/e2e/data/infrastructure-docker/main/bases/cluster-with-hcp.yaml @@ -29,7 +29,7 @@ metadata: namespace: ${NAMESPACE} spec: replicas: ${CONTROL_PLANE_MACHINE_COUNT} - version: v1.30.1-k0s.0 + version: v1.30.1+k0s.0 service: type: NodePort diff --git a/e2e/data/infrastructure-docker/main/cluster-template-ingress/cluster-with-ingress.yaml b/e2e/data/infrastructure-docker/main/cluster-template-ingress/cluster-with-ingress.yaml index 53d4a9dcb..61aace63d 100644 --- a/e2e/data/infrastructure-docker/main/cluster-template-ingress/cluster-with-ingress.yaml +++ b/e2e/data/infrastructure-docker/main/cluster-template-ingress/cluster-with-ingress.yaml @@ -27,7 +27,7 @@ kind: K0smotronControlPlane metadata: name: ${CLUSTER_NAME}-cp spec: - version: v1.34.1-k0s.0 + version: v1.34.1+k0s.0 ingress: apiHost: kube-api.${KIND_IP}.nip.io konnectivityHost: konnectivity.${KIND_IP}.nip.io diff --git a/e2e/data/infrastructure-docker/main/cluster-template-remote-hcp/hcp-with-external-cluster-patch.yaml b/e2e/data/infrastructure-docker/main/cluster-template-remote-hcp/hcp-with-external-cluster-patch.yaml index 215e6741c..d7feecf00 100644 --- a/e2e/data/infrastructure-docker/main/cluster-template-remote-hcp/hcp-with-external-cluster-patch.yaml +++ b/e2e/data/infrastructure-docker/main/cluster-template-remote-hcp/hcp-with-external-cluster-patch.yaml @@ -9,7 +9,7 @@ spec: namespace: ${NAMESPACE} key: value replicas: ${CONTROL_PLANE_MACHINE_COUNT} - version: v1.30.1-k0s.0 + version: v1.30.1+k0s.0 service: type: NodePort diff --git a/inttest/capi-docker-clusterclass-k0smotron/capi_docker_clusterclass_k0smotron_test.go b/inttest/capi-docker-clusterclass-k0smotron/capi_docker_clusterclass_k0smotron_test.go index cf1ee1029..c1d405037 100644 --- a/inttest/capi-docker-clusterclass-k0smotron/capi_docker_clusterclass_k0smotron_test.go +++ b/inttest/capi-docker-clusterclass-k0smotron/capi_docker_clusterclass_k0smotron_test.go @@ -113,7 +113,7 @@ func (s *CAPIDockerClusterClassK0smotronSuite) TestCAPIDocker() { kcp.Status.UnavailableReplicas == 0 && kcp.Status.Ready && kcp.Status.UpdatedReplicas == 2 && - kcp.Status.Version == "v1.27.2" + kcp.Status.Version == "v1.27.2+k0s.0" return ready, nil }) diff --git a/inttest/capi-docker/capi_docker_test.go b/inttest/capi-docker/capi_docker_test.go index e65bc615e..060793df1 100644 --- a/inttest/capi-docker/capi_docker_test.go +++ b/inttest/capi-docker/capi_docker_test.go @@ -258,7 +258,7 @@ kind: K0smotronControlPlane metadata: name: docker-test-cp spec: - version: v1.31.2-k0s.0 + version: v1.31.2+k0s.0 certificateRefs: - name: docker-test-ca type: ca diff --git a/inttest/capi-remote-machine-job-provision/capi_remote_machine_job_provision_test.go b/inttest/capi-remote-machine-job-provision/capi_remote_machine_job_provision_test.go index 1951db0a1..3c1ca45e0 100644 --- a/inttest/capi-remote-machine-job-provision/capi_remote_machine_job_provision_test.go +++ b/inttest/capi-remote-machine-job-provision/capi_remote_machine_job_provision_test.go @@ -274,7 +274,7 @@ metadata: name: remote-test namespace: default spec: - version: v1.27.2-k0s.0 + version: v1.27.2+k0s.0 persistence: type: emptyDir service: diff --git a/inttest/capi-remote-machine/capi_remote_machine_test.go b/inttest/capi-remote-machine/capi_remote_machine_test.go index aca715994..8d27f4ea7 100644 --- a/inttest/capi-remote-machine/capi_remote_machine_test.go +++ b/inttest/capi-remote-machine/capi_remote_machine_test.go @@ -291,7 +291,7 @@ metadata: name: remote-test namespace: default spec: - version: {{ .K0SVersion }}-k0s.0 + version: {{ .K0SVersion }}+k0s.0 persistence: type: emptyDir service: diff --git a/inttest/config-update-hcp/config_update_test.go b/inttest/config-update-hcp/config_update_test.go index 24b63ef9b..439345c35 100644 --- a/inttest/config-update-hcp/config_update_test.go +++ b/inttest/config-update-hcp/config_update_test.go @@ -205,7 +205,7 @@ metadata: "namespace": "kmc-test" }, "spec": { - "version": "v1.31.5-k0s.0", + "version": "v1.31.5+k0s.0", "service":{ "type": "NodePort" }, diff --git a/inttest/ha-controller-etcd/ha_controller_etcd_test.go b/inttest/ha-controller-etcd/ha_controller_etcd_test.go index 301e62825..08c4b4a40 100644 --- a/inttest/ha-controller-etcd/ha_controller_etcd_test.go +++ b/inttest/ha-controller-etcd/ha_controller_etcd_test.go @@ -133,7 +133,7 @@ func (s *HAControllerEtcdSuite) createK0smotronCluster(ctx context.Context, kc * }, "spec": { "replicas": 3, - "version": "v1.31.2-k0s.0", + "version": "v1.31.2+k0s.0", "service":{ "type": "NodePort" }, @@ -161,7 +161,7 @@ func (s *HAControllerEtcdSuite) updateK0smotronCluster(ctx context.Context, rc * crdRestClient, err := rest.UnversionedRESTClientFor(&crdConfig) s.Require().NoError(err) - patch := `[{"op": "replace", "path": "/spec/version", "value": "v1.31.5-k0s.0"}]` + patch := `[{"op": "replace", "path": "/spec/version", "value": "v1.31.5+k0s.0"}]` res := crdRestClient. Patch(types.JSONPatchType). Resource("clusters"). diff --git a/inttest/ha-controller-secret/ha_controller_secret_test.go b/inttest/ha-controller-secret/ha_controller_secret_test.go index cee220ab9..86f9793ec 100644 --- a/inttest/ha-controller-secret/ha_controller_secret_test.go +++ b/inttest/ha-controller-secret/ha_controller_secret_test.go @@ -149,7 +149,7 @@ func (s *HAControllerSecretSuite) createK0smotronClusterWithSecretRef(ctx contex }, "spec": { "replicas": 3, - "version": "v1.31.5-k0s.0", + "version": "v1.31.5+k0s.0", "service":{ "type": "NodePort" }, diff --git a/inttest/hostpath/hostpath_test.go b/inttest/hostpath/hostpath_test.go index bac0085bd..be0676d41 100644 --- a/inttest/hostpath/hostpath_test.go +++ b/inttest/hostpath/hostpath_test.go @@ -123,7 +123,7 @@ func (s *HostPathSuite) createK0smotronCluster(ctx context.Context, kc *kubernet "service":{ "type": "NodePort" }, - "version": "v1.31.5-k0s.0", + "version": "v1.31.5+k0s.0", "persistence": { "type": "hostPath", "hostPath": "/tmp/kmc-test" diff --git a/inttest/jointoken/jointoken_test.go b/inttest/jointoken/jointoken_test.go index be4ff3ad8..dfacd4a0a 100644 --- a/inttest/jointoken/jointoken_test.go +++ b/inttest/jointoken/jointoken_test.go @@ -108,7 +108,7 @@ func (s *JoinTokenSuite) createK0smotronCluster(ctx context.Context, kc *kuberne "namespace": "kmc-test" }, "spec": { - "version": "v1.31.5-k0s.0", + "version": "v1.31.5+k0s.0", "k0sConfig": { "apiVersion": "k0s.k0sproject.io/v1beta1", "kind": "ClusterConfig", diff --git a/inttest/monitoring/monitoring_test.go b/inttest/monitoring/monitoring_test.go index ddcd54b98..bd8f6e942 100644 --- a/inttest/monitoring/monitoring_test.go +++ b/inttest/monitoring/monitoring_test.go @@ -134,7 +134,7 @@ func (s *MonitoringSuite) createK0smotronCluster(ctx context.Context, kc *kubern "namespace": "kmc-test" }, "spec": { - "version": "v1.31.5-k0s.0", + "version": "v1.31.5+k0s.0", "monitoring": { "enabled": true }, diff --git a/inttest/pvc/pvc_test.go b/inttest/pvc/pvc_test.go index 1fd3a2482..011c5120b 100644 --- a/inttest/pvc/pvc_test.go +++ b/inttest/pvc/pvc_test.go @@ -163,7 +163,7 @@ func (s *PVCSuite) createK0smotronCluster(ctx context.Context, kc *kubernetes.Cl "namespace": "kmc-test" }, "spec": { - "version": "v1.31.5-k0s.0", + "version": "v1.31.5+k0s.0", "etcd":{ "persistence": {"size": "50Mi", "storageClass": "seaweedfs-storage"} }, diff --git a/inttest/scaling-etcd/scaling_etcd_test.go b/inttest/scaling-etcd/scaling_etcd_test.go index 2d328875f..b620dc6c4 100644 --- a/inttest/scaling-etcd/scaling_etcd_test.go +++ b/inttest/scaling-etcd/scaling_etcd_test.go @@ -200,7 +200,7 @@ var clusterResource = ` }, "spec": { "replicas": 1, - "version": "v1.31.5-k0s.0", + "version": "v1.31.5+k0s.0", "service":{ "type": "NodePort" }, diff --git a/templates/cluster-template-hcp.yaml b/templates/cluster-template-hcp.yaml index 65c39e081..ca09bd920 100644 --- a/templates/cluster-template-hcp.yaml +++ b/templates/cluster-template-hcp.yaml @@ -24,7 +24,7 @@ metadata: name: ${CLUSTER_NAME}-control-plane spec: replicas: ${CONTROL_PLANE_MACHINE_COUNT} - version: ${KUBERNETES_VERSION}-k0s.0 + version: ${KUBERNETES_VERSION}+k0s.0 persistence: type: emptyDir service: