Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
969138b
docs: add cluster-api v1.9.11 upgrade design spec
schegi Apr 1, 2026
a02cd77
Merge branch 'main' of https://github.com/ionos-cloud/cluster-api-pro…
schegi Apr 1, 2026
80cae03
docs: add cluster-api v1.9.11 upgrade implementation plan
schegi Apr 1, 2026
94f12b0
chore: bump cluster-api to v1.9.11 and controller-runtime to v0.19.6
schegi Apr 1, 2026
b17559b
chore: add importas alias for cluster-api util/conditions/v1beta2
schegi Apr 1, 2026
b30341b
feat(api): add v1beta2 conditions support to IonosCloudCluster
schegi Apr 1, 2026
fe2c00e
feat(api): add v1beta2 conditions to IonosCloudMachine, remove deprec…
schegi Apr 1, 2026
ae66a2b
chore: regenerate deepcopy and CRD manifests for v1beta2 conditions
schegi Apr 1, 2026
33fc4f3
feat(scope): migrate cluster conditions to v1beta2 API
schegi Apr 1, 2026
a8f4dcd
feat(scope): migrate machine conditions to v1beta2 API, remove HasFailed
schegi Apr 1, 2026
9a024a0
feat(controller): fix predicate signatures and migrate cluster condit…
schegi Apr 1, 2026
926b377
feat(controller): migrate machine conditions to v1beta2, remove dead …
schegi Apr 1, 2026
7221ab4
feat(cloud): migrate server conditions to v1beta2 API
schegi Apr 1, 2026
83cbe3f
chore(e2e): update CAPI component URLs to v1.9.11
schegi Apr 1, 2026
7097446
chore: regenerate mocks and fix build after cluster-api v1.9 upgrade
schegi Apr 1, 2026
9cb522b
fix(scope): add IonosCloudClusterReady to WithOwnedV1Beta2Conditions
schegi Apr 1, 2026
78b371d
chore: remove plan and spec docs
schegi Apr 1, 2026
4b756f2
review: add meaningful Message fields to all conditions.Set calls
Copilot Apr 2, 2026
98104d8
review: address PR feedback from piepmatz
schegi Apr 8, 2026
303b67d
review: dual-write v1beta1 and v1beta2 conditions
schegi Apr 8, 2026
66aa22b
review: use ReadyV1Beta2Condition constant in v1beta2 contexts
schegi Apr 8, 2026
cb902f3
plan: remove v1beta1 conditions backwards compatibility
schegi Apr 9, 2026
d22c362
feat(api): flatten v1beta2 conditions into Status.Conditions for clus…
schegi Apr 9, 2026
b63845c
refactor(controller): remove v1beta1 conditions from cluster controller
schegi Apr 9, 2026
4283ded
refactor(service): remove v1beta1 conditions from server service
schegi Apr 9, 2026
4f09093
test(api): update condition tests to use metav1.Condition
schegi Apr 9, 2026
cd2e8e9
chore: regenerate CRDs and deepcopy after condition migration
schegi Apr 9, 2026
b8c4301
Merge branch 'main' into feat/upgrade-cluster-api-v1.9.11
schegi Apr 9, 2026
07ecdc1
chore: remove condition migration plan
schegi Apr 9, 2026
dc984b5
chore(deps): bump indirect deps to fix security vulnerabilities
schegi Apr 9, 2026
e1f4784
chore: bump controller-runtime to v0.19.7 and update README CAPI vers…
Copilot Apr 9, 2026
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
4 changes: 3 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ linters-settings:
alias: clusterv1
- pkg: "sigs.k8s.io/cluster-api/test/e2e"
alias: capie2e
- pkg: "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
alias: conditions
# Own module
- pkg: github.com/ionos-cloud/cluster-api-provider-ionoscloud/api/v1alpha1
alias: infrav1
Expand Down Expand Up @@ -146,7 +148,7 @@ issues:
- linters:
- revive
text: "comment-spacings: no space between comment delimiter and comment text"
source: "//(\\+(kubebuilder|optional|required)|#nosec)"
source: "//(\\+(kubebuilder|optional|required|listType|listMapKey)|#nosec)"
- linters:
- revive
text: "exported: exported method .*\\.(Reconcile|SetupWithManager|SetupWebhookWithManager) should have comment or be unexported"
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,14 @@ This provider requires **Go 1.25 or newer**. The exact version is specified in `

This provider's versions are compatible with the following versions of Cluster API:

| | Cluster API v1beta1 (v1.7) | Cluster API v1beta1 (v1.8) |
|-----------------------|:--------------------------:|:--------------------------:|
| CAPIC v1alpha1 (v0.2) | ✓ | ☓ |
| CAPIC v1alpha1 (v0.3) | ✓ | ☓ |
| CAPIC v1alpha1 (v0.4) | ✓ | ✓ |
| CAPIC v1alpha1 (v0.5) | ✓ | ✓ |
| CAPIC v1alpha1 (v0.6) | ✓ | ✓ |
| | Cluster API v1beta1 (v1.7) | Cluster API v1beta1 (v1.8) | Cluster API v1beta1 (v1.9) |
|-----------------------|:--------------------------:|:--------------------------:|:--------------------------:|
| CAPIC v1alpha1 (v0.2) | ✓ | ☓ | ☓ |
| CAPIC v1alpha1 (v0.3) | ✓ | ☓ | ☓ |
| CAPIC v1alpha1 (v0.4) | ✓ | ✓ | ☓ |
| CAPIC v1alpha1 (v0.5) | ✓ | ✓ | ☓ |
| CAPIC v1alpha1 (v0.6) | ✓ | ✓ | ☓ |
| CAPIC v1alpha1 (v0.7) | ☓ | ☓ | ✓ |

### Kubernetes Versions

Expand Down
14 changes: 8 additions & 6 deletions api/v1alpha1/ionoscloudcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const (
ClusterFinalizer = "ionoscloudcluster.infrastructure.cluster.x-k8s.io"

// IonosCloudClusterReady is the condition for the IonosCloudCluster, which indicates that the cluster is ready.
IonosCloudClusterReady clusterv1.ConditionType = "ClusterReady"
IonosCloudClusterReady = "ClusterReady"

// IonosCloudClusterKind is the string resource kind of the IonosCloudCluster resource.
IonosCloudClusterKind = "IonosCloudCluster"
Expand Down Expand Up @@ -70,7 +70,9 @@ type IonosCloudClusterStatus struct {

// Conditions defines current service state of the IonosCloudCluster.
//+optional
Conditions clusterv1.Conditions `json:"conditions,omitempty"`
//+listType=map
//+listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty"`

// CurrentRequestByDatacenter maps data center IDs to a pending provisioning request made during reconciliation.
//+optional
Expand Down Expand Up @@ -114,13 +116,13 @@ func init() {
objectTypes = append(objectTypes, &IonosCloudCluster{}, &IonosCloudClusterList{})
}

// GetConditions returns the conditions from the status.
func (i *IonosCloudCluster) GetConditions() clusterv1.Conditions {
// GetV1Beta2Conditions returns the v1beta2 conditions from the status.
func (i *IonosCloudCluster) GetV1Beta2Conditions() []metav1.Condition {
return i.Status.Conditions
}

// SetConditions sets the conditions in the status.
func (i *IonosCloudCluster) SetConditions(conditions clusterv1.Conditions) {
// SetV1Beta2Conditions sets the v1beta2 conditions in the status.
func (i *IonosCloudCluster) SetV1Beta2Conditions(conditions []metav1.Condition) {
i.Status.Conditions = conditions
}
Comment on lines +119 to +127

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

New v1beta2 condition accessor methods (GetV1Beta2Conditions/SetV1Beta2Conditions) are introduced here, but there are no tests covering the expected behavior (returning nil when Status.V1Beta2 is nil, initializing Status.V1Beta2 on Set, and preserving list-map semantics). Adding a small unit test alongside existing status/conditions tests would help prevent regressions.

Copilot uses AI. Check for mistakes.

Expand Down
21 changes: 15 additions & 6 deletions api/v1alpha1/ionoscloudcluster_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/util/conditions"
conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
"sigs.k8s.io/controller-runtime/pkg/client"

. "github.com/onsi/ginkgo/v2"
Expand All @@ -38,11 +38,16 @@ const (
)

func TestIonosCloudCluster_Conditions(t *testing.T) {
conds := clusterv1.Conditions{{Type: "type"}}
conds := []metav1.Condition{{
Type: "type",
Status: metav1.ConditionTrue,
Reason: "reason",
LastTransitionTime: metav1.Now(),
}}
cluster := &IonosCloudCluster{}

cluster.SetConditions(conds)
require.Equal(t, conds, cluster.GetConditions())
cluster.SetV1Beta2Conditions(conds)
require.Equal(t, conds, cluster.GetV1Beta2Conditions())
}

func defaultCluster() *IonosCloudCluster {
Expand Down Expand Up @@ -163,7 +168,11 @@ var _ = Describe("IonosCloudCluster", func() {
fetched.Status.CurrentRequestByDatacenter = map[string]ProvisioningRequest{
"123": wantProvisionRequest,
}
conditions.MarkTrue(fetched, clusterv1.ReadyCondition)
conditions.Set(fetched, metav1.Condition{
Type: clusterv1.ReadyV1Beta2Condition,
Status: metav1.ConditionTrue,
Reason: clusterv1.ReadyV1Beta2Condition,
})

By("updating the cluster status")
Expect(k8sClient.Status().Update(context.Background(), fetched)).To(Succeed())
Expand All @@ -173,7 +182,7 @@ var _ = Describe("IonosCloudCluster", func() {
Expect(fetched.Status.CurrentRequestByDatacenter).To(HaveLen(1))
Expect(fetched.Status.CurrentRequestByDatacenter["123"]).To(Equal(wantProvisionRequest))
Expect(fetched.Status.Conditions).To(HaveLen(1))
Expect(conditions.IsTrue(fetched, clusterv1.ReadyCondition)).To(BeTrue())
Expect(fetched.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue))

By("Removing the entry from the status again")
delete(fetched.Status.CurrentRequestByDatacenter, "123")
Expand Down
54 changes: 8 additions & 46 deletions api/v1alpha1/ionoscloudmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import (
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/errors"

"github.com/ionos-cloud/cluster-api-provider-ionoscloud/internal/util/ptr"
)
Expand All @@ -38,7 +36,7 @@ const (

// MachineProvisionedCondition documents the status of the provisioning of a IonosCloudMachine and
// the underlying VM.
MachineProvisionedCondition clusterv1.ConditionType = "MachineProvisioned"
MachineProvisionedCondition = "MachineProvisioned"

// WaitingForClusterInfrastructureReason (Severity=Info) indicates that the IonosCloudMachine is currently
// waiting for the cluster infrastructure to become ready.
Expand Down Expand Up @@ -296,47 +294,11 @@ type IonosCloudMachineStatus struct {
//+optional
MachineNetworkInfo *MachineNetworkInfo `json:"machineNetworkInfo,omitempty"`

// FailureReason will be set in the event that there is a terminal problem
// reconciling the Machine and will contain a succinct value suitable
// for machine interpretation.
//
// This field should not be set for transitive errors that a controller
// faces that are expected to be fixed automatically over
// time (like service outages), but instead indicate that something is
// fundamentally wrong with the Machine's spec or the configuration of
// the controller, and that manual intervention is required. Examples
// of terminal errors would be invalid combinations of settings in the
// spec, values that are unsupported by the controller, or the
// responsible controller itself being critically misconfigured.
//
// Any transient errors that occur during the reconciliation of IonosCloudMachines
// can be added as events to the IonosCloudMachine object and/or logged in the
// controller's output.
//+optional
FailureReason *errors.MachineStatusError `json:"failureReason,omitempty"`

// FailureMessage will be set in the event that there is a terminal problem
// reconciling the Machine and will contain a more verbose string suitable
// for logging and human consumption.
//
// This field should not be set for transitive errors that a controller
// faces that are expected to be fixed automatically over
// time (like service outages), but instead indicate that something is
// fundamentally wrong with the Machine's spec or the configuration of
// the controller, and that manual intervention is required. Examples
// of terminal errors would be invalid combinations of settings in the
// spec, values that are unsupported by the controller, or the
// responsible controller itself being critically misconfigured.
//
// Any transient errors that occur during the reconciliation of IonosCloudMachines
// can be added as events to the IonosCloudMachine object and/or logged in the
// controller's output.
//+optional
FailureMessage *string `json:"failureMessage,omitempty"`

// Conditions defines current service state of the IonosCloudMachine.
//+optional
Conditions clusterv1.Conditions `json:"conditions,omitempty"`
//+listType=map
//+listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty"`

// CurrentRequest shows the current provisioning request for any
// cloud resource that is being provisioned.
Expand Down Expand Up @@ -406,13 +368,13 @@ type IonosCloudMachineList struct {
Items []IonosCloudMachine `json:"items"`
}

// GetConditions returns the observations of the operational state of the IonosCloudMachine resource.
func (m *IonosCloudMachine) GetConditions() clusterv1.Conditions {
// GetV1Beta2Conditions returns the v1beta2 conditions from the status.
func (m *IonosCloudMachine) GetV1Beta2Conditions() []metav1.Condition {
return m.Status.Conditions
}

// SetConditions sets the underlying service state of the IonosCloudMachine to the predescribed clusterv1.Conditions.
func (m *IonosCloudMachine) SetConditions(conditions clusterv1.Conditions) {
// SetV1Beta2Conditions sets the v1beta2 conditions in the status.
func (m *IonosCloudMachine) SetV1Beta2Conditions(conditions []metav1.Condition) {
m.Status.Conditions = conditions
}
Comment on lines +371 to +379

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

New v1beta2 condition accessor methods (GetV1Beta2Conditions/SetV1Beta2Conditions) are introduced here, but there are no unit tests validating the nil->allocated transition and round-tripping of the conditions slice. Please add tests similar to the existing v1beta1 conditions tests to prevent regressions (especially around Status.V1Beta2 initialization).

Copilot uses AI. Check for mistakes.

Expand Down
22 changes: 13 additions & 9 deletions api/v1alpha1/ionoscloudmachine_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ import (
sdk "github.com/ionos-cloud/sdk-go/v6"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/cluster-api/errors"
"sigs.k8s.io/cluster-api/util/conditions"
conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/ionos-cloud/cluster-api-provider-ionoscloud/internal/util/ptr"
Expand Down Expand Up @@ -535,17 +534,20 @@ var _ = Describe("IonosCloudMachine Tests", func() {
Expect(k8sClient.Get(
context.Background(), client.ObjectKey{Name: m.Name, Namespace: m.Namespace}, m)).To(Succeed())

// Calls SetConditions with required fields
conditions.MarkTrue(m, MachineProvisionedCondition)
conditions.Set(m, metav1.Condition{
Type: MachineProvisionedCondition,
Status: metav1.ConditionTrue,
Reason: MachineProvisionedCondition,
})

Expect(k8sClient.Status().Update(context.Background(), m)).To(Succeed())
Expect(k8sClient.Get(context.Background(),
client.ObjectKey{Name: m.Name, Namespace: m.Namespace}, m)).To(Succeed())

machineConditions := m.GetConditions()
machineConditions := m.GetV1Beta2Conditions()
Expect(machineConditions).To(HaveLen(1))
Expect(machineConditions[0].Type).To(Equal(MachineProvisionedCondition))
Expect(machineConditions[0].Status).To(Equal(corev1.ConditionTrue))
Expect(machineConditions[0].Status).To(Equal(metav1.ConditionTrue))
})
})
Context("Status", func() {
Expand All @@ -556,14 +558,16 @@ var _ = Describe("IonosCloudMachine Tests", func() {
client.ObjectKey{Name: m.Name, Namespace: m.Namespace}, m)).To(Succeed())

m.Status.Ready = true
conditions.MarkTrue(m, MachineProvisionedCondition)
conditions.Set(m, metav1.Condition{
Type: MachineProvisionedCondition,
Status: metav1.ConditionTrue,
Reason: MachineProvisionedCondition,
})
m.Status.CurrentRequest = &ProvisioningRequest{
Method: "GET",
RequestPath: "path/to/resource",
State: sdk.RequestStatusRunning,
}
m.Status.FailureReason = ptr.To(errors.InvalidConfigurationMachineError)
m.Status.FailureMessage = ptr.To("Failure message")
m.Status.Location = "de/fra"

m.Status.MachineNetworkInfo = &MachineNetworkInfo{
Expand Down
17 changes: 3 additions & 14 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -133,48 +133,63 @@ spec:
conditions:
description: Conditions defines current service state of the IonosCloudCluster.
items:
description: Condition defines an observation of a Cluster API resource
operational state.
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
Last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when
the API field changed is acceptable.
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
A human readable message indicating details about the transition.
This field may be empty.
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
The reason for the condition's last transition in CamelCase.
The specific API may choose whether or not this field is considered a guaranteed API.
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
type: string
severity:
description: |-
Severity provides an explicit classification of Reason code, so the users or machines can immediately
understand the current situation and act accordingly.
The Severity field MUST be set only when Status=False.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: Status of the condition, one of True, False, Unknown.
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: |-
Type of condition in CamelCase or in foo.example.com/CamelCase.
Many .condition.type values are consistent across resources like Available, but because arbitrary conditions
can be useful (see .node.status.conditions), the ability to deconflict is important.
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
controlPlaneEndpointIPBlockID:
description: ControlPlaneEndpointIPBlockID is the IONOS Cloud UUID
for the control plane endpoint IP block.
Expand Down
Loading
Loading