From 509c4353aa2aa241bf6545f726f78ec2534252b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCttler?= Date: Wed, 28 Jan 2026 13:54:41 +0100 Subject: [PATCH] Add ROBOT_USE_LEGACY_PROVIDER_ID to use legacy provider ID For some clusters it might be challenging to update the tool creating new nodes (like Cluster API Provider Hetzner) and the CCM so that both agree on the provider ID format. If the env var ROBOT_USE_LEGACY_PROVIDER_ID is set to a true value ("1", "t", "T", "true", "TRUE", "True"), then the CCM will use the legacy format ("hcloud://bm-NNNN") when returning InstanceMetadata. This applies only, when the node is created. Once a provider ID is set, it won't be changed again. --- .gitignore | 1 + hcloud/instances.go | 8 ++++- hcloud/instances_test.go | 49 +++++++++++++++++++++++++++++++ internal/config/config.go | 21 ++++++++----- internal/providerid/providerid.go | 5 ++++ 5 files changed, 75 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 0fd693953..cdd2977c3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ hcloud-cloud-controller-manager *.tgz hack/.* coverage/ +.vscode diff --git a/hcloud/instances.go b/hcloud/instances.go index dbed05cca..3589d49af 100644 --- a/hcloud/instances.go +++ b/hcloud/instances.go @@ -374,8 +374,14 @@ func (s robotServer) IsShutdown() (bool, error) { } func (s robotServer) Metadata(_ int64, node *corev1.Node, cfg config.HCCMConfiguration) (*cloudprovider.InstanceMetadata, error) { + var providerID string + if cfg.Robot.UseLegacyProviderID { + providerID = providerid.FromRobotLegacyServerNumber(s.ServerNumber) + } else { + providerID = providerid.FromRobotServerNumber(s.ServerNumber) + } return &cloudprovider.InstanceMetadata{ - ProviderID: providerid.FromRobotServerNumber(s.ServerNumber), + ProviderID: providerID, InstanceType: getInstanceTypeOfRobotServer(s.Server), NodeAddresses: robotNodeAddresses(s.Server, node, cfg, s.recorder), Zone: getZoneOfRobotServer(s.Server), diff --git a/hcloud/instances_test.go b/hcloud/instances_test.go index 9c08d57f3..6556998c2 100644 --- a/hcloud/instances_test.go +++ b/hcloud/instances_test.go @@ -420,6 +420,55 @@ func TestInstances_InstanceMetadataRobotServer(t *testing.T) { } } +func TestInstances_InstanceMetadataRobotServer_UseLegacyProviderID(t *testing.T) { + env := newTestEnv() + defer env.Teardown() + env.Mux.HandleFunc("/robot/server/321", func(w http.ResponseWriter, _ *http.Request) { + json.NewEncoder(w).Encode(hrobotmodels.ServerResponse{ + Server: hrobotmodels.Server{ + ServerIP: "233.252.0.123", + ServerIPv6Net: "2a01:f48:111:4221::", + ServerNumber: 321, + Product: "Robot Serverâ„¢ 1", + Name: "robot-server1", + Dc: "NBG1-DC1", + }, + }) + }) + + env.Cfg.Robot.UseLegacyProviderID = true + + instances := newInstances(env.Client, env.RobotClient, env.Recorder, 0, env.Cfg) + + metadata, err := instances.InstanceMetadata(context.TODO(), &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "robot-server1", + }, + Spec: corev1.NodeSpec{ProviderID: "hrobot://321"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + expectedMetadata := &cloudprovider.InstanceMetadata{ + ProviderID: "hcloud://bm-321", + InstanceType: "Robot-Server-1", + NodeAddresses: []corev1.NodeAddress{ + {Type: corev1.NodeHostName, Address: "robot-server1"}, + {Type: corev1.NodeExternalIP, Address: "233.252.0.123"}, + }, + Zone: "nbg1-dc1", + Region: "nbg1", + AdditionalLabels: map[string]string{ + "instance.hetzner.cloud/provided-by": "robot", + }, + } + + if !reflect.DeepEqual(metadata, expectedMetadata) { + t.Fatalf("Expected metadata %+v but got %+v", *expectedMetadata, *metadata) + } +} + func TestNodeAddresses(t *testing.T) { tests := []struct { name string diff --git a/internal/config/config.go b/internal/config/config.go index 05f97504e..eec1dda1c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -21,12 +21,13 @@ const ( hcloudNetwork = "HCLOUD_NETWORK" hcloudDebug = "HCLOUD_DEBUG" - robotEnabled = "ROBOT_ENABLED" - robotUser = "ROBOT_USER" - robotPassword = "ROBOT_PASSWORD" - robotCacheTimeout = "ROBOT_CACHE_TIMEOUT" - robotRateLimitWaitTime = "ROBOT_RATE_LIMIT_WAIT_TIME" - robotForwardInternalIPs = "ROBOT_FORWARD_INTERNAL_IPS" + robotEnabled = "ROBOT_ENABLED" + robotUser = "ROBOT_USER" + robotPassword = "ROBOT_PASSWORD" + robotCacheTimeout = "ROBOT_CACHE_TIMEOUT" + robotRateLimitWaitTime = "ROBOT_RATE_LIMIT_WAIT_TIME" + robotForwardInternalIPs = "ROBOT_FORWARD_INTERNAL_IPS" + robotUseLegacyProviderID = "ROBOT_USE_LEGACY_PROVIDER_ID" hcloudInstancesAddressFamily = "HCLOUD_INSTANCES_ADDRESS_FAMILY" @@ -51,7 +52,8 @@ type RobotConfiguration struct { CacheTimeout time.Duration RateLimitWaitTime time.Duration // ForwardInternalIPs is enabled by default. - ForwardInternalIPs bool + ForwardInternalIPs bool + UseLegacyProviderID bool } type MetricsConfiguration struct { @@ -156,7 +158,10 @@ func Read() (HCCMConfiguration, error) { } // Robot needs to be enabled cfg.Robot.ForwardInternalIPs = cfg.Robot.ForwardInternalIPs && cfg.Robot.Enabled - + cfg.Robot.UseLegacyProviderID, err = getEnvBool(robotUseLegacyProviderID, false) + if err != nil { + errs = append(errs, err) + } cfg.Metrics.Enabled, err = getEnvBool(hcloudMetricsEnabled, true) if err != nil { errs = append(errs, err) diff --git a/internal/providerid/providerid.go b/internal/providerid/providerid.go index 35a7751ba..a4c9721a5 100644 --- a/internal/providerid/providerid.go +++ b/internal/providerid/providerid.go @@ -81,3 +81,8 @@ func FromCloudServerID(serverID int64) string { func FromRobotServerNumber(serverNumber int) string { return fmt.Sprintf("%s%d", prefixRobot, serverNumber) } + +// FromRobotLegacyServerNumber generates the canonical ProviderID for a Robot Server. +func FromRobotLegacyServerNumber(serverNumber int) string { + return fmt.Sprintf("%s%d", prefixRobotLegacy, serverNumber) +}