Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a958fc0
:sparkles: Support upstream hcloud-ccm. ProviderID changes to `hrobot…
guettli Nov 3, 2025
9e0dd80
... make generate.
guettli Nov 3, 2025
42e9483
fix grammar.
guettli Nov 3, 2025
619dc53
ProviderID of infra machines should not change: Validate that.
guettli Nov 4, 2025
88de8f0
make generate.
guettli Nov 4, 2025
2185a46
added docs, rename vars for better mgt-cluster vs wl-cluster understa…
guettli Nov 4, 2025
1f6ea11
create both keys in the secret: hcloud and token.
guettli Nov 4, 2025
09a030a
add note about source of secret.
guettli Nov 4, 2025
990a5b5
create both secrets for compatibilty.
guettli Nov 4, 2025
0e3c069
added a comment to hcloud change.
guettli Nov 4, 2025
dec7bfe
wording.
guettli Nov 4, 2025
b375950
inlined creation of ProviderID, to reduce complexity.
guettli Nov 5, 2025
5f62c63
Merge remote-tracking branch 'origin/main' into tg/support-provider-i…
guettli Nov 5, 2025
40d4fe9
fix typos.
guettli Nov 5, 2025
1fd26b9
make geneate.
guettli Nov 5, 2025
cdca0c3
updated comment about "token"
guettli Nov 5, 2025
4aea658
be explicit: recommend the defaults of upstream hcloud-ccm.
guettli Nov 5, 2025
a6979e0
make makefile target `install-ccm-in-wl-cluster` work again.
guettli Nov 5, 2025
3268799
Merge branch 'main' into tg/support-provider-id-format-of-upstream-ccm
guettli Nov 11, 2025
343c68d
Merge remote-tracking branch 'origin/main' into tg/support-provider-i…
guettli Nov 13, 2025
623f05a
added comment: we create up to three times the HLCOUD_KEY in the secret.
guettli Nov 14, 2025
c28ffdf
generate-manifests.
guettli Nov 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,5 @@ watchall-output*
/conditions*.txt

/.config

/.kube
16 changes: 10 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -192,18 +192,21 @@ install-cilium-in-wl-cluster:
-f templates/cilium/cilium.yaml


install-ccm-in-wl-cluster:
install-ccm-in-wl-cluster: $(WORKER_CLUSTER_KUBECONFIG)
ifeq ($(BUILD_IN_CONTAINER),true)
docker run --rm \
-e CLUSTER_NAME=$(CLUSTER_NAME) \
-e KUBECONFIG=$(KUBECONFIG) \
-v $(shell go env GOPATH)/pkg:/go/pkg$(MOUNT_FLAGS) \
-v $(shell pwd):/src/cluster-api-provider-$(INFRA_PROVIDER)$(MOUNT_FLAGS) \
$(BUILDER_IMAGE):$(BUILDER_IMAGE_VERSION) $@;
else
helm repo add syself https://charts.syself.com
helm repo update syself
KUBECONFIG=$(WORKER_CLUSTER_KUBECONFIG) helm upgrade --install ccm syself/ccm-hetzner --version 2.0.1 \
--namespace kube-system \
--set privateNetwork.enabled=$(PRIVATE_NETWORK)
helm repo add hcloud https://charts.hetzner.cloud
helm repo update hcloud
KUBECONFIG=$(WORKER_CLUSTER_KUBECONFIG) helm install hccm \
hcloud/hcloud-cloud-controller-manager -n kube-system \
--set privateNetwork.enabled=$(PRIVATE_NETWORK) \
--set robot.enabled=true
@echo 'run "kubectl --kubeconfig=$(WORKER_CLUSTER_KUBECONFIG) ..." to work with the new target cluster'
endif

Expand Down Expand Up @@ -437,6 +440,7 @@ $(ARTIFACTS):
$(MGT_CLUSTER_KUBECONFIG):
./hack/get-kubeconfig-of-management-cluster.sh

.PHONY: $(WORKER_CLUSTER_KUBECONFIG)
$(WORKER_CLUSTER_KUBECONFIG):
./hack/get-kubeconfig-of-workload-cluster.sh

Expand Down
8 changes: 6 additions & 2 deletions api/v1beta1/hetznerbaremetalmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,12 @@ const (

// HetznerBareMetalMachineSpec defines the desired state of HetznerBareMetalMachine.
type HetznerBareMetalMachineSpec struct {
// ProviderID will be the hetznerbaremetalmachine which is set by the controller
// in the `hcloud://bm-<server-id>` format.
// ProviderID is set by the controller in the `hrobot://<server-id>` format. Before CAPH v1.1.0
// the ProviderID had the format `hcloud://bm-NNNNN`. Starting with CAPH v1.1.0 this was changed
// to `hrobot://NNNNN`. This aligns with the upstream hcloud CCM. In the long run we want to
// discontinue our CCM fork. After the ProviderID was set once, it never changes. It is safe to
// update the CCM in a running workload-cluster. Only new nodes will get the new format.
//
// +optional
ProviderID *string `json:"providerID,omitempty"`

Expand Down
9 changes: 9 additions & 0 deletions api/v1beta1/hetznerbaremetalmachine_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,14 @@ func validateHetznerBareMetalMachineSpecUpdate(oldSpec, newSpec HetznerBareMetal
)
}

if oldSpec.ProviderID != nil && *oldSpec.ProviderID != "" {
// once the Provider was set, the value must not change.
if newSpec.ProviderID == nil || *oldSpec.ProviderID != *newSpec.ProviderID {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec", "providerID"), newSpec.ProviderID, "providerID immutable"),
)
}
}

return allErrs
}
43 changes: 43 additions & 0 deletions api/v1beta1/hetznerbaremetalmachine_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ limitations under the License.
package v1beta1

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)

func TestValidateHetznerBareMetalMachineSpecCreate(t *testing.T) {
Expand Down Expand Up @@ -429,3 +432,43 @@ func TestValidateHetznerBareMetalMachineSpecUpdate(t *testing.T) {
})
}
}

func TestValidateHetznerBareMetalMachineSpecUpdate_ProviderID(t *testing.T) {
got := validateHetznerBareMetalMachineSpecUpdate(
HetznerBareMetalMachineSpec{
ProviderID: ptr.To("provider://foo"),
},
HetznerBareMetalMachineSpec{})
require.Equal(t, `[spec.providerID: Invalid value: "null": providerID immutable]`, fmt.Sprintf("%+v", got))

got = validateHetznerBareMetalMachineSpecUpdate(
HetznerBareMetalMachineSpec{
ProviderID: ptr.To("provider://foo"),
},
HetznerBareMetalMachineSpec{
ProviderID: ptr.To("provider://bar"),
})
require.Equal(t, `[spec.providerID: Invalid value: "provider://bar": providerID immutable]`, fmt.Sprintf("%+v", got))

// Allowed Updates
got = validateHetznerBareMetalMachineSpecUpdate(
HetznerBareMetalMachineSpec{},
HetznerBareMetalMachineSpec{})
require.Equal(t, `[]`, fmt.Sprintf("%+v", got))

got = validateHetznerBareMetalMachineSpecUpdate(
HetznerBareMetalMachineSpec{},
HetznerBareMetalMachineSpec{
ProviderID: ptr.To("provider://bar"),
})
require.Equal(t, `[]`, fmt.Sprintf("%+v", got))

got = validateHetznerBareMetalMachineSpecUpdate(
HetznerBareMetalMachineSpec{
ProviderID: ptr.To("provider://bar"),
},
HetznerBareMetalMachineSpec{
ProviderID: ptr.To("provider://bar"),
})
require.Equal(t, `[]`, fmt.Sprintf("%+v", got))
}
40 changes: 32 additions & 8 deletions api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,19 @@ type HCloudPlacementGroupStatus struct {

// HetznerSecretRef defines all the names of the secret and the relevant keys needed to access Hetzner API.
type HetznerSecretRef struct {
// Name defines the name of the secret.
// +kubebuilder:default=hetzner
// Name defines the name of the secret. The name gets used for reading the credential in the
// mgt-cluster, and it gets used for creating a secret in the wl-cluster. About the secret in
// the wl-cluster: Attention, the upstream hcloud-ccm helm chart expects the name to be
// "hcloud". The Syself ccm defaults to "hetzner". For compatibility with upstream hcloud-ccm
// the controller creates two secrets, if the name is different from "hcloud" (one with name
// "hcloud", one with name being the value of this setting). The secret will be created in the
// namespace "kube-system" of the workload-cluster. We recommend to use "hcloud", because this is
// the default of upstream hcloud-ccm. Set `spec.skipCreatingHetznerSecretInWorkloadCluster`, if
// you don't want that secret in the wl-cluster to be created.
//
// +kubebuilder:default=hcloud
Name string `json:"name"`

// Key defines the keys that are used in the secret.
// Need to specify either HCloudToken or both HetznerRobotUser and HetznerRobotPassword.
Key HetznerSecretKeyRef `json:"key"`
Expand All @@ -112,19 +122,33 @@ type HetznerSecretRef struct {
// HetznerSecretKeyRef defines the key name of the HetznerSecret.
// Need to specify either HCloudToken or both HetznerRobotUser and HetznerRobotPassword.
type HetznerSecretKeyRef struct {
// HCloudToken defines the name of the key where the token for the Hetzner Cloud API is stored.
// hcloudToken defines the name of the key where the token for the Hetzner Cloud API is stored.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

capital "HCloudToken"

// We recommend to use "token", because this is the default of upstream hcloud-ccm, while the
// legacy Syself ccm uses "hcloud". For maximal compatibility up to three keys get created in the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's write "Syself fork" instead of "Syself ccm"

// secret for HCLOUD_TOKEN: "hcloud", "token" and the value of hcloudToken. This way we ensure
// that the ccm in the wl-cluster finds the secret.
//
// +optional
// +kubebuilder:default=hcloud-token
// +kubebuilder:default=token
HCloudToken string `json:"hcloudToken"`
// HetznerRobotUser defines the name of the key where the username for the Hetzner Robot API is stored.

// HetznerRobotUser defines the name of the key where the username for the Hetzner Robot API is
// stored. We recommend to use "robot-user", because this is the default of upstream hcloud-ccm.
//
// +optional
// +kubebuilder:default=hetzner-robot-user
// +kubebuilder:default=robot-user
HetznerRobotUser string `json:"hetznerRobotUser"`
// HetznerRobotPassword defines the name of the key where the password for the Hetzner Robot API is stored.

// HetznerRobotPassword defines the name of the key where the password for the Hetzner Robot API
// is stored. We recommend to use "robot-password", because this is the default of upstream
// hcloud-ccm.
//
// +optional
// +kubebuilder:default=hetzner-robot-password
// +kubebuilder:default=robot-password
HetznerRobotPassword string `json:"hetznerRobotPassword"`

// SSHKey defines the name of the ssh key.
//
// +optional
// +kubebuilder:default=hcloud-ssh-key-name
SSHKey string `json:"sshKey"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,11 @@ spec:
type: object
providerID:
description: |-
ProviderID will be the hetznerbaremetalmachine which is set by the controller
in the `hcloud://bm-<server-id>` format.
ProviderID is set by the controller in the `hrobot://<server-id>` format. Before CAPH v1.1.0
the ProviderID had the format `hcloud://bm-NNNNN`. Starting with CAPH v1.1.0 this was changed
to `hrobot://NNNNN`. This aligns with the upstream hcloud CCM. In the long run we want to
discontinue our CCM fork. After the ProviderID was set once, it never changes. It is safe to
update the CCM in a running workload-cluster. Only new nodes will get the new format.
type: string
sshSpec:
description: SSHSpec gives a reference on the secret where SSH details
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,11 @@ spec:
type: object
providerID:
description: |-
ProviderID will be the hetznerbaremetalmachine which is set by the controller
in the `hcloud://bm-<server-id>` format.
ProviderID is set by the controller in the `hrobot://<server-id>` format. Before CAPH v1.1.0
the ProviderID had the format `hcloud://bm-NNNNN`. Starting with CAPH v1.1.0 this was changed
to `hrobot://NNNNN`. This aligns with the upstream hcloud CCM. In the long run we want to
discontinue our CCM fork. After the ProviderID was set once, it never changes. It is safe to
update the CCM in a running workload-cluster. Only new nodes will get the new format.
type: string
sshSpec:
description: SSHSpec gives a reference on the secret where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,28 +246,44 @@ spec:
Need to specify either HCloudToken or both HetznerRobotUser and HetznerRobotPassword.
properties:
hcloudToken:
default: hcloud-token
description: HCloudToken defines the name of the key where
the token for the Hetzner Cloud API is stored.
default: token
description: |-
hcloudToken defines the name of the key where the token for the Hetzner Cloud API is stored.
We recommend to use "token", because this is the default of upstream hcloud-ccm, while the
legacy Syself ccm uses "hcloud". For maximal compatibility up to three keys get created in the
secret for HCLOUD_TOKEN: "hcloud", "token" and the value of hcloudToken. This way we ensure
that the ccm in the wl-cluster finds the secret.
type: string
hetznerRobotPassword:
default: hetzner-robot-password
description: HetznerRobotPassword defines the name of the
key where the password for the Hetzner Robot API is stored.
default: robot-password
description: |-
HetznerRobotPassword defines the name of the key where the password for the Hetzner Robot API
is stored. We recommend to use "robot-password", because this is the default of upstream
hcloud-ccm.
type: string
hetznerRobotUser:
default: hetzner-robot-user
description: HetznerRobotUser defines the name of the key
where the username for the Hetzner Robot API is stored.
default: robot-user
description: |-
HetznerRobotUser defines the name of the key where the username for the Hetzner Robot API is
stored. We recommend to use "robot-user", because this is the default of upstream hcloud-ccm.
type: string
sshKey:
default: hcloud-ssh-key-name
description: SSHKey defines the name of the ssh key.
type: string
type: object
name:
default: hetzner
description: Name defines the name of the secret.
default: hcloud
description: |-
Name defines the name of the secret. The name gets used for reading the credential in the
mgt-cluster, and it gets used for creating a secret in the wl-cluster. About the secret in
the wl-cluster: Attention, the upstream hcloud-ccm helm chart expects the name to be
"hcloud". The Syself ccm defaults to "hetzner". For compatibility with upstream hcloud-ccm
the controller creates two secrets, if the name is different from "hcloud" (one with name
"hcloud", one with name being the value of this setting). The secret will be created in the
namespace "kube-system" of the workload-cluster. We recommend to use "hcloud", because this is
the default of upstream hcloud-ccm. Set `spec.skipCreatingHetznerSecretInWorkloadCluster`, if
you don't want that secret in the wl-cluster to be created.
type: string
required:
- key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,30 +275,44 @@ spec:
Need to specify either HCloudToken or both HetznerRobotUser and HetznerRobotPassword.
properties:
hcloudToken:
default: hcloud-token
description: HCloudToken defines the name of the key
where the token for the Hetzner Cloud API is stored.
default: token
description: |-
hcloudToken defines the name of the key where the token for the Hetzner Cloud API is stored.
We recommend to use "token", because this is the default of upstream hcloud-ccm, while the
legacy Syself ccm uses "hcloud". For maximal compatibility up to three keys get created in the
secret for HCLOUD_TOKEN: "hcloud", "token" and the value of hcloudToken. This way we ensure
that the ccm in the wl-cluster finds the secret.
type: string
hetznerRobotPassword:
default: hetzner-robot-password
description: HetznerRobotPassword defines the name
of the key where the password for the Hetzner Robot
API is stored.
default: robot-password
description: |-
HetznerRobotPassword defines the name of the key where the password for the Hetzner Robot API
is stored. We recommend to use "robot-password", because this is the default of upstream
hcloud-ccm.
type: string
hetznerRobotUser:
default: hetzner-robot-user
description: HetznerRobotUser defines the name of
the key where the username for the Hetzner Robot
API is stored.
default: robot-user
description: |-
HetznerRobotUser defines the name of the key where the username for the Hetzner Robot API is
stored. We recommend to use "robot-user", because this is the default of upstream hcloud-ccm.
type: string
sshKey:
default: hcloud-ssh-key-name
description: SSHKey defines the name of the ssh key.
type: string
type: object
name:
default: hetzner
description: Name defines the name of the secret.
default: hcloud
description: |-
Name defines the name of the secret. The name gets used for reading the credential in the
mgt-cluster, and it gets used for creating a secret in the wl-cluster. About the secret in
the wl-cluster: Attention, the upstream hcloud-ccm helm chart expects the name to be
"hcloud". The Syself ccm defaults to "hetzner". For compatibility with upstream hcloud-ccm
the controller creates two secrets, if the name is different from "hcloud" (one with name
"hcloud", one with name being the value of this setting). The secret will be created in the
namespace "kube-system" of the workload-cluster. We recommend to use "hcloud", because this is
the default of upstream hcloud-ccm. Set `spec.skipCreatingHetznerSecretInWorkloadCluster`, if
you don't want that secret in the wl-cluster to be created.
type: string
required:
- key
Expand Down
9 changes: 6 additions & 3 deletions controllers/csr_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,8 @@ func getHbmmWithConstantHostname(ctx context.Context, csrUsername string, cluste
log := ctrl.LoggerFrom(ctx)

clusterFromCSR, serverID := getServerIDFromConstantHostname(ctx, csrUsername, clusterName)
providerID := "hcloud://bm-" + serverID
legacyProviderID := "hcloud://bm-" + serverID
providerID := "hrobot://" + serverID
hList := &infrav1.HetznerBareMetalMachineList{}
selector := labels.NewSelector()
req, err := labels.NewRequirement(clusterv1.ClusterNameLabel, selection.Equals, []string{clusterFromCSR})
Expand All @@ -348,13 +349,15 @@ func getHbmmWithConstantHostname(ctx context.Context, csrUsername string, cluste
if hList.Items[i].Spec.ProviderID == nil {
continue
}
if *hList.Items[i].Spec.ProviderID == providerID {
if *hList.Items[i].Spec.ProviderID == providerID ||
*hList.Items[i].Spec.ProviderID == legacyProviderID {
hbmm = &hList.Items[i]
break
}
}

if hbmm == nil {
return nil, fmt.Errorf("ProviderID: %q %w", providerID, errNoHetznerBareMetalMachineByProviderIDFound)
return nil, fmt.Errorf("ProviderID: %q (or %q): %w", providerID, legacyProviderID, errNoHetznerBareMetalMachineByProviderIDFound)
}

log.Info("Found HetznerBareMetalMachine with constant hostname", "csr-username", csrUsername, "hetznerBareMetalMachine", hbmm.Name)
Expand Down
Loading
Loading