Repository navigation
Conversation
- 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>
Code reviewFound 1 issue:
This helper's own design doc says the check should assert cluster-api-provider-ionoscloud/test/e2e/helpers/conditions.go Lines 68 to 81 in 23768cc 🤖 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>
…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>
The base branch was changed.
## 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 |
There was a problem hiding this comment.
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(), |
There was a problem hiding this comment.
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?
| -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") |
There was a problem hiding this comment.
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.
- 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>
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>
|
There was a problem hiding this comment.
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
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.
| // 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), |
| - 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 |
| - name: v0.8.99 | ||
| value: "../../../config/default" |
Pull request was closed






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.ionoscloud.yamlas upgrade waypoints; v1.12.10 comes from the [PSC-5539] feat: upgrade cluster-api to v1.12.10 #380 basetest/e2e/helpers/{conditions,clusterhealth}.gowith theAssertConditionsMigration,AssertCAPIV1Beta2Migration, andAssertV1Beta1ClusterAndMachinesHealthypost-upgrade hookstest/e2e/upgrade_test.gowith threeDescribeblocks wrappingcapie2e.ClusterctlUpgradeSpecmake test-e2e-upgrade+ a weeklye2e-upgradeworkflow; excludes theupgradelabel from the default e2e runCluster.AvailableEvery block ends with a
v1.11.11 → v1.12.10core hop (thev0.6.99provider stays put — it is already built against v1.12.10), so the suite exercises thev1.11 → v1.12upgrade on top of the existing landings.Contract metadata
test/e2e/data/shared/v1.12/metadata.yamldeclares 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) — seecmd/clusterctl/client/cluster/upgrader.go. The v1beta1ionoscloud:v0.6.99infra provider remains compatible because the v1beta2 contract is temporarily compatible with v1beta1.Prerequisites
v0.6.3CAPIC GitHub release.v0.7.0CAPIC release (CAPIC at CAPI v1.10 state). The blocks fail atInitWithInfrastructureProvidersuntil those releases exist.Test plan
go build -tags e2e ./test/e2e/.../go vet -tags e2ecompile clean🤖 Generated with Claude Code