From d50f85f7a28073c076129268ff9b0869bdab2894 Mon Sep 17 00:00:00 2001 From: Felix Breuer Date: Mon, 26 Jan 2026 16:42:08 +0100 Subject: [PATCH] use kubernetes.io as default label Signed-off-by: Felix Breuer --- docs/stackit-iaas-api-analysis.md | 16 ++++++---------- pkg/client/sdk_test.go | 6 ++---- pkg/provider/apis/validation/validation.go | 6 +++--- .../validation/validation_core_labels_test.go | 11 +++++++++++ pkg/provider/core.go | 5 ++--- pkg/provider/create.go | 8 ++++---- pkg/provider/list.go | 4 ++-- pkg/provider/list_test.go | 10 +++++----- test/e2e/e2e_labels_test.go | 5 ++--- 9 files changed, 37 insertions(+), 34 deletions(-) diff --git a/docs/stackit-iaas-api-analysis.md b/docs/stackit-iaas-api-analysis.md index f9477e89..f21be440 100644 --- a/docs/stackit-iaas-api-analysis.md +++ b/docs/stackit-iaas-api-analysis.md @@ -213,21 +213,17 @@ Example: `stackit://my-project-123/550e8400-e29b-41d4-a716-446655440000` Use the `labels` field for MCM identification and mapping: -| Label Key | Value | Purpose | -| --------------------------------- | ----------------- | ----------------------------------------------- | -| `mcm.gardener.cloud/cluster` | Cluster ID | Identify which cluster owns this server | -| `mcm.gardener.cloud/machine` | Machine CR name | Map server to Kubernetes Machine | -| `mcm.gardener.cloud/machineclass` | MachineClass name | Map server to MachineClass for orphan detection | -| `mcm.gardener.cloud/role` | "node" | Identify as cluster node | +| Label Key | Value | Purpose | +| ---------------------------- | ----------------- | ----------------------------------------------- | +| `kubernetes.io/machine` | Machine CR name | Map server to Kubernetes Machine | +| `kubernetes.io/machineclass` | MachineClass name | Map server to MachineClass for orphan detection | Example labels: ```json { - "mcm.gardener.cloud/cluster": "shoot-dev-01", - "mcm.gardener.cloud/machine": "worker-pool-a-12345", - "mcm.gardener.cloud/machineclass": "worker-pool-a", - "mcm.gardener.cloud/role": "node" + "kubernetes.io/machine": "worker-pool-a-12345", + "kubernetes.io/machineclass": "worker-pool-a" } ``` diff --git a/pkg/client/sdk_test.go b/pkg/client/sdk_test.go index 6e46fd04..7096d295 100644 --- a/pkg/client/sdk_test.go +++ b/pkg/client/sdk_test.go @@ -116,16 +116,14 @@ var _ = Describe("SDK Type Conversion Helpers", func() { It("should convert labels with special characters", func() { labels := map[string]string{ - "mcm.gardener.cloud/machine": "test-machine", - "kubernetes.io/role": "node", + "kubernetes.io/machine": "test-machine", } result := convertLabelsToSDK(labels) Expect(result).NotTo(BeNil()) Expect(result).To(HaveLen(2)) - Expect(result["mcm.gardener.cloud/machine"]).To(Equal("test-machine")) - Expect(result["kubernetes.io/role"]).To(Equal("node")) + Expect(result["kubernetes.io/machine"]).To(Equal("test-machine")) }) }) diff --git a/pkg/provider/apis/validation/validation.go b/pkg/provider/apis/validation/validation.go index 8106845f..09a463b4 100644 --- a/pkg/provider/apis/validation/validation.go +++ b/pkg/provider/apis/validation/validation.go @@ -38,9 +38,9 @@ var regionRegex = regexp.MustCompile(`^[a-z0-9]+$`) // Pattern: lowercase letters/digits followed by digits, dash, then digit(s) (e.g., eu01-1, eu01-2) var availabilityZoneRegex = regexp.MustCompile(`^[a-z0-9]+-\d+$`) -// labelKeyRegex validates Kubernetes label keys (must start/end with alphanumeric, can contain -, _, .) +// labelKeyRegex validates Kubernetes label keys (must start/end with alphanumeric, can contain -, _, ., /) // Maximum length: 63 characters -var labelKeyRegex = regexp.MustCompile(`^[a-zA-Z0-9]([-a-zA-Z0-9_.]*[a-zA-Z0-9])?$`) +var labelKeyRegex = regexp.MustCompile(`^[a-zA-Z0-9]([-a-zA-Z0-9_./]*[a-zA-Z0-9])?$`) // labelValueRegex validates Kubernetes label values (must start/end with alphanumeric, can contain -, _, ., can be empty) // Maximum length: 63 characters @@ -113,7 +113,7 @@ func ValidateProviderSpecNSecret(spec *api.ProviderSpec, secrets *corev1.Secret) errors = append(errors, fmt.Errorf("providerSpec.labels key '%s' exceeds maximum length of 63 characters", key)) } if !labelKeyRegex.MatchString(key) { - errors = append(errors, fmt.Errorf("providerSpec.labels key '%s' has invalid format (must start/end with alphanumeric, can contain -, _, .)", key)) + errors = append(errors, fmt.Errorf("providerSpec.labels key '%s' has invalid format (must start/end with alphanumeric, can contain -, _, ., /)", key)) } if len(value) > 63 { errors = append(errors, fmt.Errorf("providerSpec.labels value for key '%s' exceeds maximum length of 63 characters", key)) diff --git a/pkg/provider/apis/validation/validation_core_labels_test.go b/pkg/provider/apis/validation/validation_core_labels_test.go index eb5a926f..61bfba87 100644 --- a/pkg/provider/apis/validation/validation_core_labels_test.go +++ b/pkg/provider/apis/validation/validation_core_labels_test.go @@ -102,6 +102,7 @@ var _ = Describe("ValidateProviderSpecNSecret", func() { "app.kubernetes.io_component": "worker", "environment-type": "prod", "version": "v1.2.3", + "app/component": "core", } errors := ValidateProviderSpecNSecret(providerSpec, secret) Expect(errors).To(BeEmpty()) @@ -168,6 +169,16 @@ var _ = Describe("ValidateProviderSpecNSecret", func() { Expect(errors[0].Error()).To(ContainSubstring("invalid format")) }) + It("should succeed with label keys containing slashes", func() { + providerSpec.Labels = map[string]string{ + "mycompany.com/environment": "prod", + "app.io/version": "v2", + "team/project/name": "web", + } + errors := ValidateProviderSpecNSecret(providerSpec, secret) + Expect(errors).To(BeEmpty()) + }) + It("should fail when label key contains invalid characters", func() { providerSpec.Labels = map[string]string{ "invalid@key": "value", diff --git a/pkg/provider/core.go b/pkg/provider/core.go index d5020049..1cfacc0c 100644 --- a/pkg/provider/core.go +++ b/pkg/provider/core.go @@ -11,9 +11,8 @@ import ( const ( StackitProviderName = "stackit" - StackitMachineLabel = "mcm.gardener.cloud/machine" - StackitMachineClassLabel = "mcm.gardener.cloud/machineclass" - StackitRoleLabel = "mcm.gardener.cloud/role" + StackitMachineLabel = "kubernetes.io/machine" + StackitMachineClassLabel = "kubernetes.io/machineclass" ) // GetVolumeIDs extracts volume IDs from PersistentVolume specs diff --git a/pkg/provider/create.go b/pkg/provider/create.go index c0578267..ab9897e2 100644 --- a/pkg/provider/create.go +++ b/pkg/provider/create.go @@ -4,6 +4,7 @@ import ( "context" "encoding/base64" "fmt" + "maps" "slices" "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" @@ -94,16 +95,15 @@ func (p *Provider) CreateMachine(ctx context.Context, req *driver.CreateMachineR func (p *Provider) createServerRequest(req *driver.CreateMachineRequest, providerSpec *api.ProviderSpec) *client.CreateServerRequest { // Build labels: merge ProviderSpec labels with MCM-specific labels labels := make(map[string]string) + // Start with user-provided labels from ProviderSpec if providerSpec.Labels != nil { - for k, v := range providerSpec.Labels { - labels[k] = v - } + maps.Copy(labels, providerSpec.Labels) } + // Add MCM-specific labels for server identification and orphan VM detection labels[StackitMachineLabel] = req.Machine.Name labels[StackitMachineClassLabel] = req.MachineClass.Name - labels[StackitRoleLabel] = "node" // Create server request createReq := &client.CreateServerRequest{ diff --git a/pkg/provider/list.go b/pkg/provider/list.go index 1d421ba0..e96b84ae 100644 --- a/pkg/provider/list.go +++ b/pkg/provider/list.go @@ -13,7 +13,7 @@ import ( // ListMachines lists all STACKIT servers that belong to the specified MachineClass // // This method retrieves all servers in the STACKIT project and filters them based on -// the "mcm.gardener.cloud/machineclass" label. This enables the MCM safety controller +// the "kubernetes.io/machineclass" label. This enables the MCM safety controller // to detect and clean up orphan VMs that are not backed by Machine CRs. // // Returns: @@ -51,7 +51,7 @@ func (p *Provider) ListMachines(ctx context.Context, req *driver.ListMachinesReq } // Filter servers by MachineClass label - // We use the "mcm.gardener.cloud/machineclass" label to identify which servers belong to this MachineClass + // We use the "kubernetes.io/machineclass" label to identify which servers belong to this MachineClass machineList := make(map[string]string) for _, server := range servers { // Generate ProviderID in format: stackit:/// diff --git a/pkg/provider/list_test.go b/pkg/provider/list_test.go index cbaf18df..162d4c80 100644 --- a/pkg/provider/list_test.go +++ b/pkg/provider/list_test.go @@ -71,23 +71,23 @@ var _ = Describe("ListMachines", func() { Context("with valid inputs", func() { It("should list machines filtered by MachineClass label", func() { mockClient.ListServersFunc = func(_ context.Context, _, _ string, selector map[string]string) ([]*client.Server, error) { - Expect(selector["mcm.gardener.cloud/machineclass"]).To(Equal("test-machine-class")) + Expect(selector["kubernetes.io/machineclass"]).To(Equal("test-machine-class")) return []*client.Server{ { ID: "server-1", Name: "machine-1", Labels: map[string]string{ - "mcm.gardener.cloud/machineclass": "test-machine-class", - "mcm.gardener.cloud/machine": "machine-1", + "kubernetes.io/machineclass": "test-machine-class", + "kubernetes.io/machine": "machine-1", }, }, { ID: "server-2", Name: "machine-2", Labels: map[string]string{ - "mcm.gardener.cloud/machineclass": "test-machine-class", - "mcm.gardener.cloud/machine": "machine-2", + "kubernetes.io/machineclass": "test-machine-class", + "kubernetes.io/machine": "machine-2", }, }, }, nil diff --git a/test/e2e/e2e_labels_test.go b/test/e2e/e2e_labels_test.go index a1d25e36..a0df4f23 100644 --- a/test/e2e/e2e_labels_test.go +++ b/test/e2e/e2e_labels_test.go @@ -510,9 +510,8 @@ spec: // Verify that MCM-generated labels would still be sent (check provider behavior) // Even without user labels, the provider should add MCM labels like: - // - mcm.gardener.cloud/machineclass - // - mcm.gardener.cloud/machine - // - mcm.gardener.cloud/role + // - kubernetes.io/machineclass + // - kubernetes.io/machine By("verifying provider handles missing labels gracefully") // Check provider logs for any errors related to missing labels