Skip to content

Commit 0dc8d6a

Browse files
authored
fix: statically map deprecated datacenter names (#1159)
The `server.datacenter` field is deprecated and will be removed from the API response after July 2026: https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters To avoid a breaking change in HCCM, we decided to statically map from the old location names to the existing datacenter names for the `topology.kubernetes.io/zone` label. For new locations we will return the location name without a `-dcxx` suffix. Deployments of `hcloud-cloud-controller-manager` that are not updated when the field is removed from the API will **panic** with the following error: "Observed a panic" panic="runtime error: invalid memory address or nil pointer dereference" panicGoValue="\"invalid memory address or nil pointer dereference\""
1 parent 74b7c6f commit 0dc8d6a

File tree

8 files changed

+109
-15
lines changed

8 files changed

+109
-15
lines changed

docs/explanation/robot-support.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ The Node controller adds information about the server to the Node object. The va
1515
- We use the lowercase variant of the location to match the Cloud Locations
1616
- `topology.kubernetes.io/zone`
1717
- Examples: `hel1-dc5` `fsn1-dc16`
18-
- We use the lowercase variant of the location to match the Cloud Datacenters
18+
- We use the lowercase variant of the location to match the Cloud (virtual) Datacenters
1919
- `instance.hetzner.cloud/provided-by`
2020
- Examples: `robot` `cloud`
2121
- We detect if the node is a Robot server or Cloud VM and set the label accordingly

go.mod

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.25.0
55
toolchain go1.25.7
66

77
require (
8-
github.com/hetznercloud/hcloud-go/v2 v2.32.0
8+
github.com/hetznercloud/hcloud-go/v2 v2.36.0
99
github.com/prometheus/client_golang v1.23.2
1010
github.com/spf13/pflag v1.0.10
1111
github.com/stretchr/testify v1.11.1
@@ -83,14 +83,14 @@ require (
8383
go.uber.org/zap v1.27.0 // indirect
8484
go.yaml.in/yaml/v2 v2.4.3 // indirect
8585
go.yaml.in/yaml/v3 v3.0.4 // indirect
86-
golang.org/x/crypto v0.45.0 // indirect
86+
golang.org/x/crypto v0.47.0 // indirect
8787
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
88-
golang.org/x/net v0.47.0 // indirect
88+
golang.org/x/net v0.49.0 // indirect
8989
golang.org/x/oauth2 v0.30.0 // indirect
90-
golang.org/x/sync v0.18.0 // indirect
91-
golang.org/x/sys v0.38.0 // indirect
92-
golang.org/x/term v0.37.0 // indirect
93-
golang.org/x/text v0.31.0 // indirect
90+
golang.org/x/sync v0.19.0 // indirect
91+
golang.org/x/sys v0.40.0 // indirect
92+
golang.org/x/term v0.39.0 // indirect
93+
golang.org/x/text v0.33.0 // indirect
9494
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
9595
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
9696
google.golang.org/grpc v1.72.2 // indirect

go.sum

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5uk
8787
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
8888
github.com/hetznercloud/hcloud-go/v2 v2.32.0 h1:BRe+k7ESdYv3xQLBGdKUfk+XBFRJNGKzq70nJI24ciM=
8989
github.com/hetznercloud/hcloud-go/v2 v2.32.0/go.mod h1:hAanyyfn9M0cMmZ68CXzPCF54KRb9EXd8eiE2FHKGIE=
90+
github.com/hetznercloud/hcloud-go/v2 v2.36.0 h1:HlLL/aaVXUulqe+rsjoJmrxKhPi1MflL5O9iq5QEtvo=
91+
github.com/hetznercloud/hcloud-go/v2 v2.36.0/go.mod h1:MnN/QJEa/RYNQiiVoJjNHPntM7Z1wlYPgJ2HA40/cDE=
9092
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
9193
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
9294
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
@@ -221,6 +223,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
221223
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
222224
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
223225
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
226+
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
227+
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
224228
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
225229
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
226230
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -233,25 +237,35 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
233237
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
234238
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
235239
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
240+
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
241+
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
236242
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
237243
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
238244
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
239245
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
240246
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
241247
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
242248
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
249+
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
250+
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
243251
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
244252
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
245253
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
246254
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
247255
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
248256
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
257+
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
258+
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
249259
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
250260
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
261+
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
262+
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
251263
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
252264
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
253265
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
254266
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
267+
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
268+
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
255269
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
256270
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
257271
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

hcloud/instances.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"k8s.io/klog/v2"
3030

3131
"github.com/hetznercloud/hcloud-cloud-controller-manager/internal/config"
32+
"github.com/hetznercloud/hcloud-cloud-controller-manager/internal/legacydatacenter"
3233
"github.com/hetznercloud/hcloud-cloud-controller-manager/internal/metrics"
3334
"github.com/hetznercloud/hcloud-cloud-controller-manager/internal/providerid"
3435
"github.com/hetznercloud/hcloud-cloud-controller-manager/internal/robot"
@@ -348,8 +349,8 @@ func (s hcloudServer) Metadata(networkID int64, _ *corev1.Node, cfg config.HCCMC
348349
ProviderID: providerid.FromCloudServerID(s.ID),
349350
InstanceType: s.ServerType.Name,
350351
NodeAddresses: hcloudNodeAddresses(networkID, s.Server, cfg),
351-
Zone: s.Datacenter.Name,
352-
Region: s.Datacenter.Location.Name,
352+
Zone: legacydatacenter.NameFromLocation(s.Location.Name),
353+
Region: s.Location.Name,
353354
AdditionalLabels: map[string]string{
354355
ProvidedBy: "cloud",
355356
},

hcloud/instances_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,8 @@ func TestInstances_InstanceMetadata(t *testing.T) {
332332
ID: 1,
333333
Name: "foobar",
334334
ServerType: schema.ServerType{Name: "asdf11"},
335-
Datacenter: schema.Datacenter{Name: "Test DC", Location: schema.Location{Name: "Test Location"}},
335+
Location: schema.Location{Name: "fsn1"},
336+
// Datacenter: nil, // API will return nil after 2026-07-01
336337
PublicNet: schema.ServerPublicNet{
337338
IPv6: schema.ServerPublicNetIPv6{
338339
IP: "2001:db8:1234::/64",
@@ -361,8 +362,8 @@ func TestInstances_InstanceMetadata(t *testing.T) {
361362
{Type: corev1.NodeHostName, Address: "foobar"},
362363
{Type: corev1.NodeExternalIP, Address: "203.0.113.7"},
363364
},
364-
Zone: "Test DC",
365-
Region: "Test Location",
365+
Zone: "fsn1-dc14",
366+
Region: "fsn1",
366367
AdditionalLabels: map[string]string{
367368
"instance.hetzner.cloud/provided-by": "cloud",
368369
},
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package legacydatacenter
2+
3+
// NameFromLocation maps location name to legacy cloud datacenter names, which
4+
// are deprecated in the API. For new locations we will return the location
5+
// name as a datacenter name (`topology.kubernetes.io/zone`).
6+
func NameFromLocation(location string) string {
7+
switch location {
8+
case "nbg1":
9+
return "nbg1-dc3"
10+
case "hel1":
11+
return "hel1-dc2"
12+
case "fsn1":
13+
return "fsn1-dc14"
14+
case "ash":
15+
return "ash-dc1"
16+
case "hil":
17+
return "hil-dc1"
18+
case "sin":
19+
return "sin-dc1"
20+
default:
21+
return location
22+
}
23+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package legacydatacenter
2+
3+
import "testing"
4+
5+
func TestNameFromLocation(t *testing.T) {
6+
tests := []struct {
7+
name string
8+
location string
9+
want string
10+
}{
11+
{
12+
name: "existing location returns dc name (ngb1)",
13+
location: "nbg1",
14+
want: "nbg1-dc3",
15+
},
16+
{
17+
name: "existing location returns dc name (hel1)",
18+
location: "hel1",
19+
want: "hel1-dc2",
20+
},
21+
{
22+
name: "existing location returns dc name (fsn1)",
23+
location: "fsn1",
24+
want: "fsn1-dc14",
25+
},
26+
{
27+
name: "existing location returns dc name (ash)",
28+
location: "ash",
29+
want: "ash-dc1",
30+
},
31+
{
32+
name: "existing location returns dc name (hil)",
33+
location: "hil",
34+
want: "hil-dc1",
35+
},
36+
{
37+
name: "existing location returns dc name (sin)",
38+
location: "sin",
39+
want: "sin-dc1",
40+
},
41+
{
42+
name: "unknown location returns location name",
43+
location: "mars",
44+
want: "mars",
45+
},
46+
}
47+
for _, tt := range tests {
48+
t.Run(tt.name, func(t *testing.T) {
49+
if got := NameFromLocation(tt.location); got != tt.want {
50+
t.Errorf("NameFromLocation() = %v, want %v", got, tt.want)
51+
}
52+
})
53+
}
54+
}

tests/e2e/cloud_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818

1919
"github.com/hetznercloud/hcloud-cloud-controller-manager/internal/annotation"
2020
"github.com/hetznercloud/hcloud-cloud-controller-manager/internal/hcops"
21+
"github.com/hetznercloud/hcloud-cloud-controller-manager/internal/legacydatacenter"
2122
"github.com/hetznercloud/hcloud-go/v2/hcloud"
2223
)
2324

@@ -37,8 +38,8 @@ func TestNodeSetCorrectNodeLabelsAndIPAddresses(t *testing.T) {
3738
labels := node.Labels
3839
expectedLabels := map[string]string{
3940
"node.kubernetes.io/instance-type": server.ServerType.Name,
40-
"topology.kubernetes.io/region": server.Datacenter.Location.Name,
41-
"topology.kubernetes.io/zone": server.Datacenter.Name,
41+
"topology.kubernetes.io/region": server.Location.Name,
42+
"topology.kubernetes.io/zone": legacydatacenter.NameFromLocation(server.Location.Name),
4243
"kubernetes.io/hostname": server.Name,
4344
"kubernetes.io/os": "linux",
4445
"kubernetes.io/arch": "amd64",

0 commit comments

Comments
 (0)