Skip to content

test(e2e): add upgrade test suite for CAPI v1beta2 and conditions migration - #379

Closed
schegi wants to merge 103 commits into
mainfrom
feat/upgrade-e2e-suite
Closed

schegi wants to merge 103 commits into
mainfrom
feat/upgrade-e2e-suite

Conversation

@schegi

@schegi schegi commented Jun 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds an e2e provider-upgrade suite (clusterctl upgrade) that validates the CAPIC conditions-schema migration and the CAPI core v1beta2 migration, landing on CAPI v1.12.10.

Stacked PR: this targets feat/upgrade-cluster-api-v1.12.10 (#380), so the local ionoscloud:v0.6.99 build under test is compiled against v1.12.10. Merge after #380.

  • Registers CAPI v1.8.12 / v1.10.10 / v1.11.11 (plus kubeadm bootstrap/control-plane) in ionoscloud.yaml as upgrade waypoints; v1.12.10 comes from the [PSC-5539] feat: upgrade cluster-api to v1.12.10 #380 base
  • Registers CAPIC v0.6.3 and v0.7.0 as init providers for the upgrade blocks
  • Adds test/e2e/helpers/{conditions,clusterhealth}.go with the AssertConditionsMigration, AssertCAPIV1Beta2Migration, and AssertV1Beta1ClusterAndMachinesHealthy post-upgrade hooks
  • Adds test/e2e/upgrade_test.go with three Describe blocks wrapping capie2e.ClusterctlUpgradeSpec
  • Adds make test-e2e-upgrade + a weekly e2e-upgrade workflow; excludes the upgrade label from the default e2e run
Block Init Upgrade path Migration under test
1 (single-hop) CAPIC v0.6.3 + CAPI v1.8.12 → v1.11.11 → v1.12.10 (CAPIC v0.6.99) CAPIC conditions schema (v1beta1 → dual v1beta2 + deprecated)
2 (multi-hop) CAPIC v0.6.3 + CAPI v1.8.12 → v1.10.10 → v1.11.11 → v1.12.10 v1beta1 staging, then CAPI core v1beta2 + Cluster.Available
3 (single-hop) CAPIC v0.7.0 + CAPI v1.10.10 → v1.11.11 → v1.12.10 CAPI core v1beta2 type / CRD storage migration

Every block ends with a v1.11.11 → v1.12.10 core hop (the v0.6.99 provider stays put — it is already built against v1.12.10), so the suite exercises the v1.11 → v1.12 upgrade on top of the existing landings.

Contract metadata

test/e2e/data/shared/v1.12/metadata.yaml declares the v1.11 and v1.12 release series as v1beta2 (matching upstream CAPI's own metadata). clusterctl resolves a provider's contract from the metadata of its latest registered version (v1.12.10) and, during upgrade, requires the core target's contract to equal the contract the in-process clusterctl build supports (v1beta2 for a client built from CAPI v1.12) — see cmd/clusterctl/client/cluster/upgrader.go. The v1beta1 ionoscloud:v0.6.99 infra provider remains compatible because the v1beta2 contract is temporarily compatible with v1beta1.

Prerequisites

  • Block 1 requires the v0.6.3 CAPIC GitHub release.
  • Block 3 requires a published v0.7.0 CAPIC release (CAPIC at CAPI v1.10 state). The blocks fail at InitWithInfrastructureProviders until those releases exist.

Test plan

  • go build -tags e2e ./test/e2e/... / go vet -tags e2e compile clean
  • Block 1 runs end-to-end (needs CAPIC v0.6.3 release)
  • Block 2 runs end-to-end
  • Block 3 runs end-to-end (needs CAPIC v0.7.0 release)
  • Confirm each block's final hop lands on CAPI v1.12.10 and the workload cluster stays healthy

🤖 Generated with Claude Code

schegi and others added 17 commits June 12, 2026 13:19
- Bump sigs.k8s.io/cluster-api and cluster-api/test to v1.11.11,
  controller-runtime to v0.21.0, k8s.io/* to v0.33.3
- Move core CAPI imports to api/core/v1beta2, IPAM to api/ipam/v1beta2,
  addons/bootstrap/controlplane test imports to their v1beta2 packages
- Adopt v1beta2 contract Stage 2 status layout on IonosCloudCluster and
  IonosCloudMachine: status.initialization.provisioned (*bool, omitzero,
  MinProperties=1 per contract conventions), top-level metav1.Conditions,
  deprecated v1beta1 fields under status.deprecated.v1beta1
- Add cluster.x-k8s.io/v1beta2 contract label to CRDs
- Wire CRDMigrator with UseStatusForStorageVersionMigration and
  concurrency 1; update patch helper owned-conditions options
- Update importas aliases for the new CAPI package layout
- Regenerate deepcopy, CRD manifests and mocks

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…or CAPI v1.11

- Point e2e core/bootstrap/control-plane components at CAPI v1.11.11
  and add the v1.11 shared clusterctl metadata
- Add 0.7 release series (contract v1beta1, matching upstream CAPI
  v1.11 metadata) for the release carrying this upgrade
- Extend the README compatibility matrix: v0.7 requires CAPI v1.11
  (the v1beta2 Stage 2 status layout drops top-level status.ready,
  which CAPI <= v1.10 relies on)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Raise the go directive to 1.26.0; bump builder image to golang:1.26.1
  and lint/docs Go version references
- Replace all ptr.To(...) call sites with the Go 1.26 extended new(expr)
  builtin and drop the now-unused ptr.To helper (ptr.Deref/ptr.Equal stay)
- Apply gopls modernize fixes: errors.As -> errors.AsType (Go 1.26),
  strings.Split(...)[0] -> strings.Cut, redundant type arguments
- Upgrade golangci-lint v1.64.8 -> v2.12.2 (v1 analyzers cannot parse
  new(expr) and reported false positives); migrate .golangci.yml to the
  v2 format, simplify lint-fix via the v2 fmt command, disable the new
  revive enforce-switch-style rule and exclude goconst in tests
- Fix findings surfaced by v2's newer linters: preallocate slices,
  remove embedded-field selectors, fmt.Fprintf over WriteString(Sprintf),
  extract kind-name constants in e2e helpers

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CAPI v1.11 upgrade changed IPAddressClaimSpec.PoolRef from
corev1.TypedLocalObjectReference to ipamv1.IPPoolReference. The new
manual mapping silently left APIGroup as empty string when
poolRef.APIGroup was nil.

IPPoolReference.APIGroup is +required with MinLength=1; an empty value
causes the API server to reject the IPAddressClaim with a CEL
validation error, permanently stalling IP allocation for the affected
machine with no clear pointer to the root cause.

Fix: return an explicit error when APIGroup is nil so the reconcile
loop surfaces the problem instead of producing an invalid API object.
…s an error

The CAPI v1.11 upgrade replaced conditions.SetSummary (void return) with
conditions.SetSummaryCondition (returns error). The new code returned
that error immediately, skipping patchHelper.Patch entirely.

The comment below the error check explicitly states the patch must
happen even when reconciliation is aborted. Skipping it means any
in-memory state changes made before PatchObject is called — deleted
pending request entries, updated network info — are never flushed to
the API server. On the next reconcile the controller reads stale data
and may enter the wrong branch.

Fix: capture the SetSummaryCondition error, always attempt the patch,
and return the first non-nil error (patch error takes precedence since
it means state was not persisted; the summary condition error is
returned only if patching succeeded).
…s an error

The CAPI v1.11 upgrade replaced conditions.SetSummary (void return)
with conditions.SetSummaryCondition (returns error). The new code
returned that error immediately, skipping patchHelper.Patch entirely.

Skipping the patch means in-memory state changes — ProviderID,
MachineNetworkInfo, Initialization.Provisioned — are never flushed to
the API server. The controller then proceeds with the reconcile loop
using state the API server never received. In the worst case, it
removes the finalizer on a machine whose status was never written.

Fix: capture the SetSummaryCondition error, always attempt the patch,
and return the first non-nil error (patch error takes precedence since
it means state was not persisted; the summary condition error is
returned only if patching succeeded).
…onditions

No production code writes MachineProvisionedCondition as a v1beta1
condition. Since CAPI v1.11, FinalizeMachineProvisioning uses
conditions.Set (v1beta2 API) exclusively for this type. Listing it in
WithOwnedV1Beta1Conditions has no effect today, but it signals false
ownership: the patch helper claims responsibility for a v1beta1
condition slot it never fills.

The result is that stale v1beta1 MachineProvisioned conditions from
machines provisioned before the CAPI v1.11 upgrade are never cleaned
up by the patch helper, because the helper only removes owned entries
when the controller writes a replacement value.

Fix: remove MachineProvisionedCondition from WithOwnedV1Beta1Conditions.
Only clusterv1.ReadyCondition is managed through the v1beta1 interface
and belongs in that list.
…dition

FinalizeMachineProvisioning called conditions.Set with
Reason: string(infrav1.MachineProvisionedCondition), recycling the
condition Type "MachineProvisioned" as its own Reason.

The CAPI v1beta2 convention requires Reason to be a distinct word
that explains WHY the condition holds — not a restatement of the type.
When CAPI mirrors this condition into the parent Machine's
InfrastructureReady summary, operators see Reason="MachineProvisioned"
which is identical to the condition type and provides no diagnostic
value. Alert rules and tooling that filter by Reason cannot distinguish
"provisioned" from "still provisioning" using this value.

All sibling conditions in the controller already follow the pattern
(WaitingForClusterInfrastructureReason, WaitingForBootstrapDataReason).

Fix: add a MachineProvisionedReason constant ("Provisioned") to the
API types and use it — together with an explicit Message — in the
conditions.Set call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rReady condition

The cluster controller called conditions.Set with
Reason: string(infrav1.IonosCloudClusterReady), recycling the condition
Type "ClusterReady" as its own Reason.

The CAPI v1beta2 convention requires Reason to be a distinct word that
explains WHY the condition holds. When CAPI mirrors this into the
parent Cluster's InfrastructureReady summary, operators see
Reason="ClusterReady" — identical to the type — which provides no
diagnostic value for monitoring or alerting.

Fix: add a dedicated reason constant to the API types and use it —
together with an explicit Message — in the conditions.Set call,
matching the convention already used for machine conditions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The CAPI v1.11 upgrade replaced conditions.MarkTrue/MarkFalse (which
required a message string) with conditions.Set (metav1.Condition),
where the Message field is optional. All call sites omitted it,
regressing from the enforced-message contract of the v1beta1 API.

conditions.SetSummaryCondition propagates Message verbatim when
aggregating into the parent Ready condition. Empty messages mean
operators inspecting kubectl describe, dashboards, or alert output
see blank diagnostic text. This is particularly harmful for the
WaitingFor conditions (False cases) where operators are actively
debugging why a machine or cluster has not progressed.

Fix: add a concise, human-readable Message to every conditions.Set
call in both controllers.
…nce conversion

The CAPI v1.11 upgrade introduced a manual mapping from
corev1.TypedLocalObjectReference to ipamv1.IPPoolReference. The same
three-field struct conversion (Name, Kind, APIGroup pointer-deref)
appeared identically in both CreateIPAddressClaim and the test helper
toIPPoolReference in ipam_test.go.

If a new field is added to IPPoolReference the conversion must be
updated in two places; a missed update silently produces different
objects in production vs. tests, masking divergence.

Note: Finding #1 changed the production path to return an error on nil
APIGroup, which diverges from the test helper's nil-ok semantics.
If the two paths remain structurally similar after that fix, extract a
shared helper; otherwise document that the duplication is resolved by
the semantic split.
…eta1Conditions and WithOwnedConditions

The CAPI v1.11 upgrade added patch.WithOwnedV1Beta1Conditions alongside
the existing patch.WithOwnedConditions in both Cluster.PatchObject and
Machine.PatchObject. The condition types in both lists are identical,
requiring maintainers to keep two parallel slices in sync.

Adding a new owned condition type requires updating two places. A
condition added only to WithOwnedConditions but forgotten in
WithOwnedV1Beta1Conditions silently leaves v1beta1 clients with stale
ownership semantics.

Fix: derive both lists from a single package-level var (or adjacent
constants) so the sync requirement is structurally enforced rather than
relying on convention.
FinalizeMachineProvisioning contained two inline nil-guard blocks to
initialize Status.Deprecated and Status.Deprecated.V1Beta1 before
setting V1Beta1.Ready = true. IonosCloudMachine already has
SetV1Beta1Conditions which performs the identical two-step nil-safe
initialization. The inline copy re-implemented the pattern, creating
two maintenance surfaces: if a field is added to
IonosCloudMachineDeprecatedStatus, the inline copy must also be found
and updated separately.

Additionally, allocating fresh structs via &IonosCloudMachineDeprecatedStatus{}
unconditionally overwrites any already-set fields in a concurrent or
re-entrant call path.

Fix: add SetV1Beta1Ready(bool) to IonosCloudMachine following the same
nil-safe pattern as SetV1Beta1Conditions, and use it in
FinalizeMachineProvisioning.
Remove double blank line in ipam.go and trailing newline in ipam_test.go
to satisfy gci formatter. Drop unused //nolint:staticcheck directives on
SetV1Beta1Ready call sites since staticcheck no longer flags them. Update
stale condition message in isInfrastructureReady to reference the v1beta2
field name Cluster.Status.Initialization.InfrastructureProvisioned.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Addresses SonarQube S1192: define a constant instead of duplicating
"unable to create controller" across three call sites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace kindIonosCloudCluster with infrav1.IonosCloudClusterKind and
the "Cluster" literals with clusterv1.ClusterKind — both constants
already exist in their respective API packages. Addresses the copilot
review finding; no upstream constant exists for the remaining literals.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Available

CAPI v1.11 VerifyClusterAvailable requires Cluster.Available to be True
with an EMPTY message. Non-empty messages on True conditions propagate
through SetSummaryCondition into the "Ready" mirror and ultimately into
Cluster.Available, causing the assertion to fail.

Root cause: the summaryMessage function in CAPI's conditions package
includes non-empty Info-priority (True) condition messages when the
overall status is True. Removing the messages from our success-state
conditions makes the summary message empty, satisfying the test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gfariasalves-ionos

Copy link
Copy Markdown
Contributor

Code review

Found 1 issue:

  1. AssertCAPIV1Beta2Migration checks the wrong CRD field to confirm storage migration completed

This helper's own design doc says the check should assert status.storedVersions contains v1beta2 ("confirms the CRD storage migration ran"). The shipped code instead checks spec.versions[].storage, which only indicates which version new writes will use — it flips instantly on upgrade and says nothing about whether pre-existing objects have actually been rewritten in etcd. status.storedVersions is the field that reflects real migration completion and only shrinks to v1beta2 once old objects are rewritten. As written, this assertion can pass even if v1beta1 objects are still stored, defeating the stated purpose of the check.

// Assert clusters.cluster.x-k8s.io CRD storage version is v1beta2.
clusterCRD := &apiextensionsv1.CustomResourceDefinition{}
Expect(c.Get(ctx, types.NamespacedName{Name: "clusters.cluster.x-k8s.io"}, clusterCRD)).
To(Succeed(), "failed to fetch clusters.cluster.x-k8s.io CRD")
storageVersion := ""
for _, v := range clusterCRD.Spec.Versions {
if v.Storage {
storageVersion = v.Name
break
}
}
Expect(storageVersion).To(Equal("v1beta2"),
"clusters.cluster.x-k8s.io CRD storage version should be v1beta2 after CAPI v1.11 upgrade")

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

…ta2 contract

Per the v1beta2 InfraMachine contract rule, spec.providerID must be a
string instead of *string. Also add the cluster.x-k8s.io/v1beta2 CRD
annotation to IonosCloudCluster/IonosCloudMachine to declare the
implemented contract version, as required by the v1.10-to-v1.11
migration guide.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WassimKallel and others added 6 commits September 23, 2026 01:37
…plan doc

Intervals: ClusterctlUpgradeSpec (this suite) reads
wait-resource-versions-remain-stable, and cluster_upgrade.go /
cluster_upgrade_runtimesdk.go / self_hosted.go read wait-control-plane-upgrade
and wait-machine-deployment-upgrade. None were defined. GetIntervals returns nil
for a missing key and Eventually treats that as Gomega's ~1s default, so the
specs would have failed with near-instant timeouts that look like product bugs.
Values match upstream's docker.yaml.

Workflow: actions/checkout, setup-go and upload-artifact were at v4/v5/v4 while
every other workflow in the repo is on v7 (bumped by #388 and #319). Aligned, so
this does not reintroduce old majors and trigger fresh dependabot PRs.

docs/superpowers: removed. The directory was deleted twice on purpose
(3ef274b "drop AI instructions + superpowers", f4c2bc7 "remove agent-generated
planning docs") and main carries none. The file is an agent working document
that also references specs #378 already deleted, and nothing links to it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Condense the explanatory comments added across the review fixes to a line or two
each. The reasoning belongs in the commit messages and PR threads, not inline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings in the trimmed comments from the base and condenses the interval
comments added here. Kept IP_FAMILY alongside the shortened note.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Jonas Riedel <138458199+jriedel-ionos@users.noreply.github.com>
Matches the new IONOSCLOUD_MACHINE_IMAGE_ID node image.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Base automatically changed from feat/upgrade-cluster-api-v1.12.10 to main September 23, 2026 11:58
@WassimKallel
WassimKallel dismissed stale reviews from tinashe-mundangepfupfu and gfariasalves-ionos September 23, 2026 11:58

The base branch was changed.

WassimKallel added a commit that referenced this pull request Sep 23, 2026
## Summary

Bumps Cluster API to
[v1.12.10](https://github.com/kubernetes-sigs/cluster-api/releases/tag/v1.12.10),
following the [v1.11-to-v1.12 migration
guide](https://cluster-api.sigs.k8s.io/developer/providers/migrations/v1.11-to-v1.12).

- `sigs.k8s.io/cluster-api{,/test}` `v1.11.11` → `v1.12.10`
- `sigs.k8s.io/controller-runtime` `v0.21.0` → `v0.22.5`
- `k8s.io/*` `v0.33.3` → `v0.34.3`

No code changes required — none of the v1.11 → v1.12 breaking changes
apply, and `make generate` / `make manifests` produce no diff.

## e2e

- Pins the management-cluster CAPI components to `v1.12.10` and adds
`test/e2e/data/shared/v1.12/metadata.yaml`.
- Splits the kind management version from the workload version.
`suite_test.go` used `KUBERNETES_VERSION` for both, and v1.30 is
workload-only under v1.12. Now `KUBERNETES_VERSION_MANAGEMENT: v1.33.7`
for kind, `KUBERNETES_VERSION: v1.30.6` unchanged for workload — so no
IONOS image rebuild.
- Upgrade-suite changes for v1.12 are in #379.

## Also

- Bumps `cel-go`, OpenTelemetry and `grpc-gateway/v2` to clear
Dependabot alerts.
- Pins `aquasecurity/trivy-action` to a commit SHA at `v0.36.0`
(SonarQube hotspot).
- README: v1.12 column, plus a note that `☓` means not validated.

## Test plan

- [x] `go build ./...`, `go vet -tags e2e ./...`, `make lint`, `make
verify` — clean
- [x] `make unit-test`, `make integration-test` — 90/90 envtest specs
pass
- [ ] e2e quickstart against CAPI v1.12.10

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Max Neverov <neverov.max@gmail.com>
Co-authored-by: Max Neverov <1296281+mneverov@users.noreply.github.com>
Co-authored-by: Wassim Kallel <wassim.kallel@ionos.com>
Co-authored-by: Jonas Riedel <138458199+jriedel-ionos@users.noreply.github.com>
Conflicts were alignment artifacts in test/e2e/config/ionoscloud.yaml:
main's squashed v1.12.10 entries are already on this branch, which adds
the older provider versions the upgrade suite needs. Kept this branch's
superset; resulting tree is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
with:
go-version-file: "go.mod"

- name: Run e2e upgrade tests

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This provisions real IONOS infra the same way e2e.yaml does, but we don't carry over the cancellation-cleanup step (id: tests + make remove-cancelled-e2e-leftovers on if: cancelled()). Without it, a cancelled or timed-out run here leaves the datacenter/IP block behind.

"(legacy v1beta1 conditions must be backfilled, see api/v1alpha1.BackfillLegacyConditionReasons)",
kind, name, cond.Type)
}
Expect(hasTrue).To(BeTrue(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This only checks that some condition is Status=True, not that the summary Ready condition specifically is. A stale MachineProvisioned=True from before the upgrade could let this pass even if Ready flipped False post-upgrade, which is exactly the regression this suite is meant to catch. Can we assert on the Ready condition explicitly?

Comment thread Makefile
-e2e.skip-resource-cleanup=$(SKIP_RESOURCE_CLEANUP) -e2e.use-existing-cluster=$(USE_EXISTING_CLUSTER)

.PHONY: test-e2e-upgrade
test-e2e-upgrade: docker-build-e2e ## Run only the provider-upgrade e2e tests (Label "upgrade")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

test-e2e forwards GINKGO_FOCUS/GINKGO_SKIP but this target drops both. Anyone trying to isolate one upgrade hop with GINKGO_FOCUS will silently run both Describe blocks instead, which is expensive given these provision real cloud resources.

WassimKallel added a commit that referenced this pull request Sep 29, 2026
- Go: go.mod requires 1.27.1, the README still said 1.26.
- CAPIC v0.6 x CAPI v1.11, v0.7 x v1.11 and v0.7 x v1.12 were marked as
  validated, but no e2e run covers these pairs: the PR e2e job only tests
  the local build, and the upgrade suite (#379) pairs v0.6.3 with v1.8.12
  and v0.7.0 with v1.10.10. Mark them as not validated, per the table's
  legend; the note below the table already says older releases may keep
  working with v1.11-v1.13 since the v1beta1 contract is still accepted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WassimKallel added a commit that referenced this pull request Sep 30, 2026
Follow-up to #398, addressing the gaps found while reviewing it. Each
fix is a separate commit so they can be reviewed (or dropped)
individually.

> Based on `main` after #398 (squash-merged as 0462e1f).

## Behaviour fixes

| # | Commit | Summary |
|---|---|---|
| 1 |
[`4b5e209`](4b5e209)
fix(controller): keep MachineProvisioned in step with provisioning |
Until the machine is provisioned, `MachineProvisioned` now shows
`Provisioning`, or `ProvisioningFailed` with the step error, instead of
keeping a stale `WaitingForBootstrapData`. That stale message is what
hid the image-lookup failure in the #379/#398 e2e runs. A provisioned
machine stays True on transient errors. Adds two reason constants and a
unit test. |
| 2 |
[`26db5b9`](26db5b9)
fix(cloud): requeue while the bootstrap secret is not found | A NotFound
bootstrap Secret returned `(false, nil)`, so
`FinalizeMachineProvisioning` could mark a worker provisioned before any
server existed. It now requeues. |
| 3 |
[`a3bc251`](a3bc251)
fix: restore the global rate limit for the provider controllers |
controller-runtime v0.23 (priority queue on by default) dropped the
global 10 qps / 100 burst bucket from the default rate limiter. It is
restored explicitly, one limiter per controller. |
| 12 |
[`a3e1926`](a3e1926)
fix(cmd): move the initFlags doc comment back above initFlags | Review
fix for commit 3: `controllerOptions` was inserted between `initFlags`
and its doc comment, so the comment documented the wrong function
(Copilot review). |
| 13 |
[`567cce1`](567cce1)
fix(controller): reconcile IonosCloudMachines on Cluster unpause | The
machine controller didn't watch Clusters, so a machine whose reconcile
saw a paused Cluster (e.g. a stale cache read right at unpause) waited
for an unrelated event. This made the Quickstart finalizer resilience
check flaky (one IonosCloudMachine never got its finalizer back). Now
watches Clusters on paused transitions and infrastructure provisioned,
as CAPD does. |

## Diagnostics

| # | Commit | Summary |
|---|---|---|
| 4 |
[`e42310a`](e42310a)
fix(cloud): report location and name filter in image lookup errors |
`found 0 images matching selector …` now also reports the location, the
version name filter, and the count before the name filter. |

## Cleanups

| # | Commit | Summary |
|---|---|---|
| 5 |
[`0402862`](0402862)
refactor(cloud): use util.IsControlPlaneMachine again | Drops the
private copy of the upstream helper. `network.go` now matches `main`. |
| 6 |
[`abf9140`](abf9140)
test(e2e): use upstream's owner-reference and finalizer maps | Replaces
local copies of framework maps; the Secret rule clones upstream's and
adds the IonosCloudCluster-owned credentials Secret. |
| 7 |
[`2449ea4`](2449ea4)
test(e2e): build the quickstart owner-reference assertions once | One
assertion list and filter shared by all quickstart checks. |
| 8 |
[`c0f652b`](c0f652b)
refactor(scope): drop no-op string() around clusterv1.ReadyCondition |
`ReadyCondition` is untyped in v1beta2. |
| 9 |
[`80a4882`](80a4882)
docs(api): drop "(Severity=Info)" from condition reason comments |
Severity is a v1beta1 concept. |

## Tooling

| # | Commit | Summary |
|---|---|---|
| 10 |
[`b6e1190`](b6e1190)
chore(build): align tool versions with CAPI v1.13 | controller-gen
v0.20.0 (CRDs change only in their version stamp), envtest 1.36.0,
setup-envtest v0.25.1 tag instead of a commit pin. |

## Documentation

| # | Commit | Summary |
|---|---|---|
| 11 |
[`453db57`](453db57)
docs: correct the Go version and unvalidated compatibility cells |
README said Go 1.26 while `go.mod` requires 1.27.1. v0.6 × CAPI v1.11,
v0.7 × v1.11 and v0.7 × v1.12 were marked ✓ although no e2e run covers
those pairs; now ☓ (not validated), per the table's legend. |

## Notes for reviewers

- New condition reasons visible to users: `Provisioning` and
`ProvisioningFailed` on `MachineProvisioned` (commit 1).
- Compatibility matrix, still open (not changed here):
- v0.8 × CAPI v1.11 / v1.12 ✓ were validated on earlier builds (#378,
#380); the current v0.8 (built on CAPI v1.13.5) is only e2e-tested
against a v1.13.5 core. Re-validate, e.g. with #379's upgrade suite,
before releasing v0.8.
- v0.7 × CAPI v1.8 / v1.9 were ✓ in the v0.7.1 release README and
flipped to ☓ in #378 without a stated reason.
- Not included on purpose: a YAML anchor for the three CAPI provider
blocks in the e2e config. A merge key placed after `value` would
silently point all three at `core-components.yaml`, so it's not worth
the edits it saves.

## Testing

- Every commit individually: `golangci-lint` 0 issues, unit tests pass.
- Tip: `make test` passes (including the API integration suite on
envtest 1.36.0), `go vet -tags e2e ./test/e2e/...` clean.
- The e2e suite runs on this PR's CI.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings September 30, 2026 10:23
@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The suite uses Kubernetes versions unsupported by its source CAPI releases and lacks cancellation cleanup for cloud resources.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity · 1 Low severity

Open (3)
What changed in this PR

Adds scheduled e2e coverage for CAPIC condition-schema and CAPI v1beta2 migrations across provider upgrades.

Changes:

  • Adds staged and isolated upgrade scenarios with migration assertions.
  • Registers historical provider versions and required test configuration.
  • Adds a dedicated Make target and weekly workflow.
File Description
test/​e2e/​upgrade_test.go Defines upgrade scenarios and hooks.
test/​e2e/​helpers/​conditions.go Validates condition and CRD migration.
test/​e2e/​helpers/​clusterhealth.go Checks v1beta1 cluster health.
test/​e2e/​config/​ionoscloud.yaml Registers upgrade versions and intervals.
Makefile Adds the upgrade test target.
.github/​workflows/​e2e-upgrade.yaml Schedules weekly upgrade tests.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread test/e2e/upgrade_test.go
Comment on lines +48 to +51
// InitWithKubernetesVersion is required by ClusterctlUpgradeSpec: it is the
// Kubernetes version of the secondary (kind) management cluster the old
// providers are installed into. Reuse the suite's configured version.
InitWithKubernetesVersion: e2eConfig.MustGetVariable(KubernetesVersion),
Comment on lines +28 to +37
- name: Run e2e upgrade tests
run: make test-e2e-upgrade

- name: Upload artifacts
uses: actions/upload-artifact@v7
if: success() || failure()
with:
name: logs-upgrade
path: _artifacts
retention-days: 7
Comment on lines 181 to 182
- name: v0.8.99
value: "../../../config/default"
auto-merge was automatically disabled September 30, 2026 11:36

Pull request was closed

This branch had an error being deployed

1 failed deployment
e2e — fc3eade6 Deployed Sep 30, 2026 by WassimKallel via e2e #687
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants