test(e2e): add chainsaw-native release upgrade testing lane - #3276
myasnikovdaniil wants to merge 19 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds reusable GitHub Actions for E2E sandbox lifecycle management and introduces a release upgrade E2E lane covering baseline installation, resource seeding, Cozystack upgrade, post-upgrade verification, and diagnostics. ChangesRelease upgrade E2E
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Workflow
participant SandboxActions
participant Sandbox
participant Chainsaw
participant Kubernetes
Workflow->>SandboxActions: download assets and prepare sandbox
SandboxActions->>Sandbox: run prepare-env
Workflow->>Sandbox: run upgrade-cozystack
Sandbox->>Chainsaw: execute seed and verify suites
Chainsaw->>Kubernetes: apply resources and run checks
Kubernetes-->>Chainsaw: readiness, data, and storage results
Workflow->>SandboxActions: collect reports and teardown
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces an automated release upgrade testing lane to ensure platform stability during version transitions. By installing the previous stable release, seeding workloads with canary data, and verifying their integrity after an upgrade to the current build, the new suite provides critical validation for migration paths. The implementation uses a modular architecture with shared composite actions and reusable health checks, ensuring that the upgrade lane remains maintainable and efficient without impacting the existing E2E pipeline's critical path. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a release upgrade E2E testing lane to verify platform upgrades from the previous latest minor stable release to the current version. It adds composite GitHub Actions for sandbox lifecycle management, BATS orchestration scripts, and Kyverno Chainsaw suites for seeding and verifying workloads (MariaDB, PostgreSQL, Redis, VMs, and tenant Kubernetes clusters). The review feedback highlights a missing seeding file for PostgreSQL (upgrade-seed-postgres), recommends adding the --fail (-f) flag to curl when downloading assets to ensure proper error handling, suggests quoting the SANDBOX_NAME variable in shell commands to prevent word splitting, and proposes simplifying heavily escaped inline scripts in the upgrade BATS tests.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| (conditions[?type == 'Ready']): | ||
| - status: "True" | ||
|
|
||
| - name: canary-intact |
There was a problem hiding this comment.
It appears that the corresponding seeding file hack/e2e-chainsaw-upgrade/seed/postgres/chainsaw-test.yaml is missing from this pull request. Without a seeding phase to initialize the upgrade_canary table and insert the test rows, the upgrade-verify-postgres verification step will fail during the upgrade test run.
| curl -sSL -H "Authorization: token ${APP_TOKEN}" -H "Accept: application/octet-stream" \ | ||
| -o _out/assets/nocloud-amd64.raw.xz \ | ||
| "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${DISK_ID}" |
There was a problem hiding this comment.
The curl command is invoked with -sSL but without -f (or --fail). If the asset download fails (e.g., due to an expired token, invalid disk ID, or network issue), curl will exit with code 0 and write the error response (often HTML or JSON) to _out/assets/nocloud-amd64.raw.xz. This causes silent failures that are harder to debug in subsequent steps. Adding -f ensures curl fails fast with a non-zero exit code on HTTP errors.
curl -fsSL -H "Authorization: token ${APP_TOKEN}" -H "Accept: application/octet-stream" \
-o _out/assets/nocloud-amd64.raw.xz \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${DISK_ID}"| run: | | ||
| cd "/tmp/$SANDBOX_NAME" | ||
| attempt=0 | ||
| until make SANDBOX_NAME=$SANDBOX_NAME prepare-env; do |
There was a problem hiding this comment.
The SANDBOX_NAME variable is unquoted when passed to make. While the sandbox name generated in this workflow is unlikely to contain spaces, it is a best practice to quote variable expansions in shell scripts to prevent word splitting and globbing issues.
until make SANDBOX_NAME="$SANDBOX_NAME" prepare-env; do| fi | ||
|
|
||
| # The stamp is set by a Job, so poll briefly rather than reading once. | ||
| timeout 120 sh -ec "until [ \"\$(kubectl get configmap cozystack-version -n cozy-system -o jsonpath='{.data.version}' 2>/dev/null)\" = \"${expected}\" ]; do sleep 3; done" || { |
There was a problem hiding this comment.
The inline script uses heavily escaped double quotes and variable expansions (\"\$(...) and ${expected}). This can be hard to read and maintain. You can simplify this by passing the expected version as an environment variable and using single quotes for the sh -ec script to avoid escaping. Additionally, the -o jsonpath argument does not require quotes if it contains no spaces.
EXPECTED_VERSION="$expected" timeout 120 sh -ec 'until [ "$(kubectl get configmap cozystack-version -n cozy-system -o jsonpath={.data.version} 2>/dev/null)" = "$EXPECTED_VERSION" ]; do sleep 3; done' || {
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
hack/e2e-chainsaw-upgrade/seed/tenant-k8s/chainsaw-test.yaml (1)
20-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd scoped diagnostics for the tenant-cluster resources.
Unlike the sibling seed suites (mariadb/postgres/redis/vm), which each attach a
describe+podLogsfor their specific resource incatch:, this test only hasevents: {}. Given this is explicitly called out as the flakiest/heaviest suite (nested KVM + DRBD + tenant Talos image import), a failure here would benefit most from adescribeof theKamajiControlPlane/TenantControlPlane/MachineDeploymenton error.♻️ Suggested catch block addition
catch: - events: {} + - describe: + apiVersion: kamaji.clastix.io/v1alpha1 + kind: KamajiControlPlane + name: kubernetes-upgrade-tk8s + - describe: + apiVersion: cluster.x-k8s.io/v1beta1 + kind: MachineDeployment + name: kubernetes-upgrade-tk8s-md0🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hack/e2e-chainsaw-upgrade/seed/tenant-k8s/chainsaw-test.yaml` around lines 20 - 22, Update the catch block in the tenant-k8s Chainsaw test to retain the existing events diagnostics and add scoped describe diagnostics for the KamajiControlPlane, TenantControlPlane, and MachineDeployment resources, matching the diagnostic structure used by the sibling seed suites.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/actions/e2e-prepare/action.yaml:
- Around line 28-35: Update the sandbox-name step around the shell variable key
to receive sandbox-suffix through the step’s env configuration, then reference
that environment variable in the shell condition and key extension instead of
directly interpolating inputs.sandbox-suffix. Preserve the existing default
behavior when the suffix is empty and the current SANDBOX_NAME and sandbox-name
outputs.
In `@hack/e2e-chainsaw-upgrade/verify/platform/chainsaw-test.yaml`:
- Around line 41-55: Validate that the baseline files exist before running
either comm comparison in the PersistentVolume and CrashLoopBackOff checks. Add
fail-fast guards for $dir/unbound-pv.txt and $dir/crashloop.txt, exiting nonzero
with an appropriate error when either is missing, while preserving the existing
PV failure gate and warning-only CrashLoopBackOff behavior.
---
Nitpick comments:
In `@hack/e2e-chainsaw-upgrade/seed/tenant-k8s/chainsaw-test.yaml`:
- Around line 20-22: Update the catch block in the tenant-k8s Chainsaw test to
retain the existing events diagnostics and add scoped describe diagnostics for
the KamajiControlPlane, TenantControlPlane, and MachineDeployment resources,
matching the diagnostic structure used by the sibling seed suites.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 6c3df8d5-fd85-4ca9-8ad1-b3245fd9eee3
📒 Files selected for processing (27)
.github/actions/e2e-collect/action.yaml.github/actions/e2e-download-assets/action.yaml.github/actions/e2e-prepare/action.yaml.github/actions/e2e-teardown/action.yaml.github/labels.yml.github/workflows/pull-requests.yamldocs/agents/e2e-testing.mdhack/e2e-chainsaw-upgrade/.chainsaw.yamlhack/e2e-chainsaw-upgrade/seed/mariadb/chainsaw-test.yamlhack/e2e-chainsaw-upgrade/seed/postgres/chainsaw-test.yamlhack/e2e-chainsaw-upgrade/seed/redis/chainsaw-test.yamlhack/e2e-chainsaw-upgrade/seed/tenant-k8s/chainsaw-test.yamlhack/e2e-chainsaw-upgrade/seed/vm/chainsaw-test.yamlhack/e2e-chainsaw-upgrade/seed/zz-baseline/chainsaw-test.yamlhack/e2e-chainsaw-upgrade/verify/mariadb/chainsaw-test.yamlhack/e2e-chainsaw-upgrade/verify/platform/chainsaw-test.yamlhack/e2e-chainsaw-upgrade/verify/postgres/chainsaw-test.yamlhack/e2e-chainsaw-upgrade/verify/redis/chainsaw-test.yamlhack/e2e-chainsaw-upgrade/verify/tenant-k8s/chainsaw-test.yamlhack/e2e-chainsaw-upgrade/verify/vm/chainsaw-test.yamlhack/e2e-install-cozystack.batshack/e2e-upgrade-apply.batshack/e2e-upgrade-install-previous.batshack/e2e-wait-hr-ready.shhack/upgrade-prev-version.shhack/upgrade-prev-version_test.batspackages/core/testing/Makefile
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/actions/e2e-prepare/action.yaml:
- Around line 3-8: Clarify the retry description in the composite action
documentation around the Talos e2e sandbox provisioning: state that the command
permits up to 3 total attempts, meaning 2 retries, and update the referenced
e2e-testing guidance citation or wording so both describe the same retry budget.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2f384a97-7f00-44c4-a126-1a327aed0c6c
📒 Files selected for processing (2)
.github/actions/e2e-prepare/action.yaml.github/workflows/pull-requests.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
- .github/workflows/pull-requests.yaml
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
hack/e2e-upgrade-install-previous.bats (1)
69-73: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winAdd existence backstops before
kubectl waitand avoid ad-hoc retries.The
kubectl waiton line 69 is missing a prior existence backstop, making it vulnerable to race conditions if theDeploymentis not yet created. Furthermore, lines 72-73 use an ad-hocuntil kubectl waitretry loop instead of the established backstop pattern.As per coding guidelines for E2E scripts and learnings, every
kubectl waitmust be guarded by an existence backstop (until kubectl get), and ad-hoc retry variations should be avoided in favor of the repository's established pattern.🛠️ Proposed fix to apply the established backstop pattern
- kubectl wait deployment/cozystack-operator -n cozy-system --timeout=2m --for=condition=Available - - # Operator installs the CRDs at startup, then creates the platform PackageSource. - timeout 120 sh -ec 'until kubectl wait crd/packages.cozystack.io --for=condition=Established --timeout=10s 2>/dev/null; do sleep 2; done' - timeout 120 sh -ec 'until kubectl wait crd/packagesources.cozystack.io --for=condition=Established --timeout=10s 2>/dev/null; do sleep 2; done' + timeout 60 sh -ec 'until kubectl get deployment/cozystack-operator -n cozy-system >/dev/null 2>&1; do sleep 2; done' + kubectl wait deployment/cozystack-operator -n cozy-system --timeout=2m --for=condition=Available + + # Operator installs the CRDs at startup, then creates the platform PackageSource. + timeout 120 sh -ec 'until kubectl get crd/packages.cozystack.io crd/packagesources.cozystack.io >/dev/null 2>&1; do sleep 2; done' + kubectl wait crd/packages.cozystack.io crd/packagesources.cozystack.io --for=condition=Established --timeout=2m🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hack/e2e-upgrade-install-previous.bats` around lines 69 - 73, Guard the deployment wait and both CRD waits with the repository’s established existence backstop: first poll using “until kubectl get” with the appropriate resource and namespace, then run “kubectl wait” once. Replace the ad-hoc “until kubectl wait” loops for packages.cozystack.io and packagesources.cozystack.io while preserving their Established condition checks and timeouts.Sources: Coding guidelines, Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@hack/e2e-upgrade-install-previous.bats`:
- Around line 69-73: Guard the deployment wait and both CRD waits with the
repository’s established existence backstop: first poll using “until kubectl
get” with the appropriate resource and namespace, then run “kubectl wait” once.
Replace the ad-hoc “until kubectl wait” loops for packages.cozystack.io and
packagesources.cozystack.io while preserving their Established condition checks
and timeouts.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c3befbf2-52d8-4344-9c1d-0a3182ef6251
📒 Files selected for processing (1)
hack/e2e-upgrade-install-previous.bats
There was a problem hiding this comment.
🧹 Nitpick comments (1)
hack/e2e-upgrade-install-previous.bats (1)
72-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAlign with the established existence backstop pattern.
The learning explicitly advises using
until kubectl getto poll for resource existence prior to a singlekubectl waitcall, rather than wrappingkubectl waitinside the polling loop itself.As per retrieved learnings: "guard kubectl wait by first polling for the target resource to exist... Prefer the repository’s established backstop pattern rather than ad-hoc one-off variations."
♻️ Proposed refactor
- timeout 120 sh -ec 'until kubectl wait crd/packages.cozystack.io --for=condition=Established --timeout=10s 2>/dev/null; do sleep 2; done' - timeout 120 sh -ec 'until kubectl wait crd/packagesources.cozystack.io --for=condition=Established --timeout=10s 2>/dev/null; do sleep 2; done' + timeout 120 sh -ec 'until kubectl get crd/packages.cozystack.io crd/packagesources.cozystack.io >/dev/null 2>&1; do sleep 2; done' + kubectl wait crd/packages.cozystack.io crd/packagesources.cozystack.io --for=condition=Established --timeout=2m timeout 120 sh -ec 'until kubectl get packagesource cozystack.cozystack-platform >/dev/null 2>&1; do sleep 2; done'🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hack/e2e-upgrade-install-previous.bats` around lines 72 - 74, Update the polling commands in the upgrade-install flow so each CRD first uses an until kubectl get existence loop, then performs a single kubectl wait for Established. Preserve the existing timeouts and resource names, and apply the repository’s established existence-backstop pattern consistently to packages.cozystack.io and packagesources.cozystack.io.Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@hack/e2e-upgrade-install-previous.bats`:
- Around line 72-74: Update the polling commands in the upgrade-install flow so
each CRD first uses an until kubectl get existence loop, then performs a single
kubectl wait for Established. Preserve the existing timeouts and resource names,
and apply the repository’s established existence-backstop pattern consistently
to packages.cozystack.io and packagesources.cozystack.io.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d89a1314-c216-4c79-8bc4-4fc3afe09e47
📒 Files selected for processing (4)
docs/agents/e2e-testing.mdhack/e2e-upgrade-install-previous.batspackages/core/platform/templates/migration-hook.yamlpackages/core/platform/values.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/agents/e2e-testing.md
64577bf to
6e0c374
Compare
IvanHunters
left a comment
There was a problem hiding this comment.
Verdict
LGTM with non-blocking notes
test(e2e): add chainsaw-native release upgrade testing lane (+1692/-90, 29 files). No CRITICAL/MAJOR defects after executing the available offline reproductions (helm template vs merge-base, dash/shellcheck on both new scripts, mutation-testing the new bats suite, source-level verification of the GitHub Actions / Chainsaw assumptions).
Findings
[MINOR] hack/upgrade-prev-version_test.bats:87-95 (guards hack/upgrade-prev-version.sh:41) — the test named "line match is dot-anchored (1.5 does not match v155.x)" is theatre. Removing the dot-escaping (sed 's/\./\\./g') still leaves all 10 tests green, because stable_desc's upstream filter already normalizes tag shapes so escaped vs unescaped patterns are equivalent (confirmed via a ~30k-combination differential check). A second mutation on the walk-down loop did correctly turn a different test red, so the rest of the suite is non-vacuous — only this one named test overclaims what it verifies.
Claim mismatches (non-blocking)
- [PARTIAL] PR body "Status" section is stale: commit
2c6b9140already fixes the canary DB access path the body says "still needs a dev-cluster run". - [UNVERIFIABLE] actionlint / live CI green — outside the hermetic toolset; the unit-tests portion was independently confirmed by re-running the bats suite.
Caveats (verification limits, not defects)
- Phase 5c corner-render for the new
migrations.etcdAdoptSkipBackuptoggle could not be executed:helm template packages/core/platformfails offline on a pre-existing unconditionallookup+failinrepository.yaml(reproduces identically on merge-base, not touched by this PR), andmigration-hook.yamlis itselflookup-gated. Residual risk judged low — the consumedETCD_ADOPT_SKIP_BACKUPenv var is pre-existing, already unit-tested, and the wiring is a single unconditional-else block with no cross-reference or dependsOn. - The
chart_lintrender_error + 2 missing_refs flagged by tooling are all confirmed pre-existing and unrelated (reproduced on merge-base; referencing templates untouched). - Both new
#!/bin/shscripts pass shell-portability checks (shellcheck --shell=sh, dash -n, no bashisms, invoked only via shebang). - Tenant-Kubernetes seed/verify suites are correctly gated off by default (
UPGRADE_E2E_TENANT_K8S); zero risk to default CI.
|
Aleksei Sviridkin (@lexfrei) both blockers are fixed, plus the corrected resolver finding from your third round — B1, contract test. This one was mine to own: I ran the unit suite before writing that commit rather than after, and B2, MariaDB header. Rewritten to Resolver, per your correction. Reproduced — Body. The red e2e is not this PR. Still open from your non-blocking list: |
6c78739 to
48ae5be
Compare
Reimplements the intent of #2401 (previous-minor-stable -> current upgrade test) over the post-Chainsaw-migration codebase. The old #2401 was a single 895-line BATS file, now obsolete; this builds a two-phase Chainsaw lane bracketing an external helm upgrade, reusing existing files throughout. Flow (make upgrade-cozystack, in the e2e sandbox): install-previous (bats, prev OCI chart) -> seed (chainsaw, skipDelete) -> helm upgrade to in-tree chart (bats) -> verify (chainsaw, assert-only). DRY over existing files: - 4 composite actions (.github/actions/e2e-{download-assets,prepare,collect, teardown}) shared by both the e2e and new upgrade-e2e jobs; the e2e job is refactored onto them (behavior-preserving). - hack/e2e-wait-hr-ready.sh: the all-HR-Ready gate extracted from e2e-install-cozystack.bats so install and upgrade share one legible gate. - hack/upgrade-prev-version.sh (+ unit test) resolves the baseline version. - seed suites apply the existing app manifests by relative path (no new CRs). Trigger: an upgrade-e2e job in pull-requests.yaml, parallel to e2e, gated on the release label or a maintainer upgrade-e2e label. Advisory / non-blocking. Coverage: postgres + mariadb canary rows, redis/VM volume survival, migration stamp advance, all-HRs-Ready + no-new-unbound-PV. The tenant Kubernetes survival suites (#2931 path) are env-gated OFF (UPGRADE_E2E_TENANT_K8S) as the flakiest surface, pending dev-stand validation. Co-Authored-By: Claude Opus 4.8 <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
Dev-cluster validation of the seed/verify canary commands surfaced two real bugs, now fixed and re-verified (postgres=3, mariadb=3 rows): - postgres: the seed writes SQL to psql over a heredoc on stdin, but `kubectl exec` without `-i` does not forward stdin, so CREATE/INSERT silently never ran. Add `-i`. - mariadb: the nano server container ships the `mariadb` client (MariaDB 11 dropped `mysql`) and does not bind 127.0.0.1 — connecting there fails and its retry OOM-kills the container. Use the `mariadb` client against the mariadb-test-primary Service (writable primary) from mariadb-test-0. Co-Authored-By: Claude Opus 4.8 <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
Pre-commit's zizmor audit flags any `>> $GITHUB_ENV` write inside a composite action as a cross-boundary env write (github-env, "may allow code execution") — even the identical line passes in a workflow file. Move the SANDBOX_NAME write back into each job as a one-liner (the pre-composite, zizmor-clean form) and have the e2e-prepare composite only copy the workspace + run prepare-env. GITHUB_JOB keeps the e2e and upgrade-e2e sandboxes distinct. Co-Authored-By: Claude Opus 4.8 <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
…dfs baseline The etcd v1alpha2 adoption snapshot cannot succeed in an e2e sandbox: it needs a trusted-cert (ACME) external S3 endpoint, which a sandbox (example.org, no DNS/ACME, self-signed in-cluster cert) cannot provide — matching the dev10 findings. Set migrations.etcdAdoptSkipBackup=true in the platform Package so migration 50 runs the real adoption without the infeasible snapshot. Keep seaweedfs on the root tenant (the historical default install-cozystack.bats also uses) for a realistic baseline, but drop the fragile cozy-backups-creds wait — the adoption no longer depends on the seaweedfs S3/bucket/creds chain. Co-Authored-By: Claude Opus 4.8 <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
The composites extracted here were written against the pre-78fc3f917 e2e job, which copied the checkout to /tmp/$SANDBOX_NAME, ran every step from there and removed it on teardown. main has since dropped that workspace: e2e is a single job on an ephemeral runner, so there is no cross-job rendezvous and the checkout is never reused. Resolving the rebase in favour of the composites would have reintroduced it, including the unguarded `rm -rf /tmp/$SANDBOX_NAME` that commit deliberately deleted rather than guarded — when an upstream step fails the unconditional `Set sandbox ID` is skipped, SANDBOX_NAME stays empty, and the `if: always()` removal expands to `rm -rf /tmp/`. Drop the Prepare/Remove workspace steps and the `cd /tmp/$SANDBOX_NAME` prefixes from e2e-prepare, e2e-collect and e2e-teardown, and point the collect and chainsaw-report upload paths at _out/. Same treatment for the upgrade-e2e job's own two `cd` prefixes. Signed-off-by: Myasnikov Daniil <[email protected]>
main gained a `labeled` pull_request trigger whose plan/resolve guards
discard every label event except `full-e2e`. This lane's documented opt-in
("any PR a maintainer opts in with the upgrade-e2e label") therefore did
nothing after the rebase: adding the label fired a `labeled` event, `plan`
skipped, `finalize` skipped with it, and upgrade-e2e never satisfied its
needs. Before the rebase there was no `labeled` trigger at all, so the label
was only ever read on push — the gap appeared with the new base, not here.
Admit `upgrade-e2e` alongside `full-e2e` in both allowlists so labelling an
open PR starts a run, and use !cancelled() rather than always() in the job
gate, matching the sibling `e2e` job: a cancelled run must not go on to
occupy a 32-vCPU runner for up to 180 minutes.
Signed-off-by: Myasnikov Daniil <[email protected]>
The previous commit broadened the plan and resolve_assets label gates to admit upgrade-e2e, but hack/promote-gate-contract.bats pins those two expressions as exact grep -cF literals. Both counts fell to zero, bats-unit-tests failed, and because "Unit & controller tests" gates finalize, the required E2E Tests check and this PR's own upgrade lane were both skipped — the commit meant to make the label start a run stopped every run instead. I broke this by running the unit suite before writing that commit rather than after; pre-commit does not cover bats-unit-tests, so it went unnoticed. Update both assertions to the broadened expressions, and add a case pinning the label end to end: admitted by the `labeled` allow-list in both plan and resolve_assets, honoured by the job's own condition, and named "Upgrade E2E Test" so it cannot collide with the required "E2E Tests" context and stop being advisory. Note the label is double-quoted inside the JSON allow-list — matching the single-quoted form counts zero and asserts nothing. Verified non-vacuous: narrowing the plan gate back to the full-e2e-only literal turns exactly one test red. Also correct the MariaDB seed header, which still prescribed the 127.0.0.1 connection that commit f598c25 replaced — the same file documents that path as OOM-killing the nano server container. Signed-off-by: Myasnikov Daniil <[email protected]>
`cut -f2` prints the whole line when that line carries no delimiter, so a single-component target like "v1" came back with min=1 and was resolved as if it were v1.1 — walking down to the v1.0 line and printing a real-looking baseline with exit 0. The lane would then install that baseline and report on an upgrade path nobody asked for, which is worse than failing. Pass -s so the delimiter-less line is suppressed, min comes back empty, and the existing parse guard rejects it. Two-component targets are unaffected: the guard rejects a missing minor, not a missing patch, and v1.6 still resolves. In practice the lane only ever passes "" or a target derived from a release-X.Y.Z branch, so this was latent rather than live. Signed-off-by: Myasnikov Daniil <[email protected]>
The case I just added was vacuous. _fixture ships no v1.0.x or v1.1.x tag, so the buggy reading of "v1" as v1.1 walked down to an empty v1.0 line and exited non-zero for the wrong reason — the assertion held whether or not the defect was present, which the mutation run showed: reverting -s left all eleven green. Give the case its own tag set containing v1.0.9, so the buggy path has a real baseline to return with exit 0 and the case can only pass because the target is refused. Reverting -s now fails this case and only this case. Signed-off-by: Myasnikov Daniil <[email protected]>
The upgrade-e2e label opt-in was restored in "make the upgrade-e2e label opt-in actually start a run" by naming the label in `plan`'s and `resolve_assets`' allowlists. Both gates still exist and that commit still applies, but main has since grown three more places that read the same label, and the opt-in is not live until all of them agree. The concurrency key routes a `labeled` event into a separate `-label` group, documented as the exact complement of `plan`'s guard so that only a run publishing nothing is moved aside. Once `plan` admits upgrade-e2e the complement is no longer exact: the run publishes, but sat in its own group, which is two live publishers of the `E2E Tests` context on one head SHA — the case the comment there warns produces a later green that erases a real failure. `e2e-report` carries the same `labeled` guard for the opposite reason, to stay in lockstep with `plan`. The hazard it documents is running while `plan` skipped; the hazard here is the reverse. `plan` overwrites `E2E Tests` with `pending` at the start of every run it executes and only `e2e-report` concludes it, so admitting the label to one and not the other opens a pending required status on an upgrade-e2e label and never closes it, leaving the PR unmergeable until someone pushes again. `verify-release-candidate` is the third, and the one with teeth. `e2e-report` needs it and, on a PR carrying the `release` label, posts `E2E Tests` = failure when its result is anything but success. So an upgrade-e2e label added to a promote PR would start `plan` and `e2e-report`, skip the candidate check between them, and stamp a red over an already-green required status — with no `unlabeled` trigger to undo it. Admitting the label there keeps the three in step. The lane stays advisory. `e2e-report` does not gain upgrade-e2e in `needs`, so the lane's result never moves the verdict, and on an upgrade-e2e-labelled run the sibling `e2e` job still supplies the real one. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
hack/bats-no-exit-trap.bats, added to main after this branch opened, is red on this file: eleven tests install `trap 'rm -rf "$tmp"' EXIT` and the file declares no debt, so `bats-unit-tests` fails, which skips `finalize` and with it both the required `E2E Tests` check and this PR's own lane. Convert rather than declare the debt. A handler installed inside an @test replaces the one the bats binary keeps for its own bookkeeping, and a test that then fails prints no TAP line at all — not `not ok`, nothing — so the suite reads green while a case is failing. Removing the scratch directory as the last statement of the body is the documented alternative: both runners set -e, so on failure the removal is unreachable and the directory survives for inspection, which is what a failed test wants anyway. The file header now says so, next to the cleanup it explains, so the next person adding a test here copies the right shape. Verified: hack/bats-no-exit-trap.bats green, and all eleven cases in this suite still pass. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
Review found the case named "line match is dot-anchored (1.5 does not match v155.x)" does not test that: removing the `sed 's/\./\\./g'` escaping from latest_on_line leaves all ten cases green. Checked it, and the reason is narrower than the report. Over the tags stable_desc emits — `^v[0-9]+\.[0-9]+\.[0-9]+$` and nothing else — the escaping, the trailing `$` and the `^` are each unfalsifiable: such a tag carries exactly one `v` and it is at the front, so no fixture can be built that the escaped and unescaped patterns disagree about. v155.0.0 in particular matches neither reading, so the old fixture pinned nothing at all. One piece of the anchoring a fixture CAN reach is the leading `^v` pair. v11.5.0 ends in "1.5.0" and `sort -rV` puts it above v1.5.3, so a pattern that has lost that prefix resolves the wrong line entirely. Swap the fixture to it, rename the case to the property it now pins, and record in both files what is and is not falsifiable here, so the next reader does not have to redo the experiment. Verified non-vacuous: dropping `^v` from latest_on_line's pattern turns exactly this case red; the other ten stay green. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
`comm -13 "$dir/unbound-pv.txt" ... || true` turns a missing baseline
file into an empty diff, and an empty diff reads as "nothing newly
broken". A verify phase run without its seed phase therefore reported
the platform healthy having compared nothing at all — the most expensive
kind of green. The sibling suites already guard theirs: verify/vm and
verify/redis fail fast with `[ -f "$before" ] || { ...; exit 1; }`.
Assert both baselines exist before either diff, and drop the `|| true`
that was swallowing the missing-file error along with everything else.
The two `kubectl get` calls had the same shape of hole one layer down: a
pipeline takes the exit status of its LAST command, so `kubectl get pv |
awk ... > file` left an empty list and a passing diff whenever the
kubectl call failed. Write the listing to a file first, where `set -e`
sees the failure.
Assisted-By: Claude <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
…e image `curl -sSL ... -o _out/assets/nocloud-amd64.raw.xz` exits 0 on an HTTP error and writes the response body into the file, so an expired token or a 404 lands as a "disk image" and surfaces minutes later as a decompression error that names nothing useful. Add --fail. The behaviour is unchanged from the code this composite was extracted from, but it now serves both the e2e and the upgrade lane, and the release PR path — the one that downloads a draft-release asset with a token that can expire — is the path the upgrade lane exists for. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
Both Chainsaw phases take their report name from .chainsaw.yaml, so both wrote chainsaw-report.xml into the same directory and the workflow always uploaded it as `upgrade-chainsaw-report`. On the passing path that is the verify report, which is what the step comment assumed. On the failing path it is whatever ran last — a lane that dies during seed publishes the SEED report under the verify name, and the case where someone is actually reading the file is exactly the failing one. Rename per phase in the make targets, on the way out, and collect both names. A phase that never ran leaves no file, which is the answer the artifact should give. `rm -f` before each run so a report left by an earlier phase cannot be renamed as this one's. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
48ae5be to
fe40b2b
Compare
The seed side carried the same fail-open the platform verify just lost: `kubectl get pods -A | awk ... > file || true` takes the exit status of the pipeline's LAST command, and the `|| true` discarded even that. A failed listing therefore wrote an EMPTY baseline and passed. An empty baseline is not silent — verify/platform reads every currently-unbound PV as newly unbound — but it fails forty minutes later, in the wrong suite, naming resources that were unbound before the upgrade ever started. Take both listings out of their pipelines so `set -e` sees them, and drop the `2>/dev/null` that was hiding the reason. The two intermediates go to /tmp rather than the baseline directory, so what the verify phase reads there is still exactly the two snapshots this suite documents. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
The headroom check derives its job list by matching workflow jobs on the prepare-env / prepare-cluster make targets, and its coverage cross-check demands that every workflow file naming packages/core/testing yield at least one such job. Moving the sandbox bring-up into .github/actions/e2e-prepare left the target inside the action and only runs-on in the workflow, so the sweep matched neither the e2e nor the upgrade-e2e job and the cross-check turned red — exactly the case the file's header named as its floor and declined to cover. Both legs now follow a local `uses: ./…` into the action file it names, and into a local action that action uses in turn, so a job is matched wherever the make target actually sits and a workflow is read together with the actions it names. A `uses:` naming a reusable workflow is deliberately not followed: .github/workflows is already swept file by file, and the calling job carries no runs-on for this check to size. The sweep now emits JOB / REF / ERROR records so both legs read one derivation instead of two that can disagree. A local reference the strict path pattern cannot read, one that resolves to no readable action.yaml, and an action that reaches itself are all refusals rather than silent drops — a marker this cannot open is a lane it cannot see. Signed-off-by: Myasnikov Daniil <[email protected]>
…s them The lane's doc listed three of the four shared composite actions, omitting the one that fetches the Talos disk and the PR patch. While correcting it, say what the extraction costs elsewhere: the sandbox bring-up marker that hack/sandbox-runner-headroom.bats matches jobs on now sits inside an action rather than in the workflow, which is why that guard follows a local `uses:`. Someone moving another step into a composite should know the guard has a stake in it. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
… red The lane is advisory, and an advisory check that is red for a reason everyone already knows trains reviewers to ignore it — then it stops working on the day it finds something new. It currently has exactly one such reason: tenant-test/vm-instance-test does not return to Ready across an upgrade (#3984), while the same workload passes on a fresh install in the very same run. Give the lane a ledger rather than a longer timeout or a suite exclusion. hack/e2e-wait-hr-ready.sh learns an optional EXPECTED_NOT_READY_FILE, and only hack/e2e-upgrade-apply.bats sets it; unset, the script behaves as it always did, down to making no second listing call on the happy path, so the install gate is untouched. The entries have to expire on their own or the file becomes the thing it was meant to prevent, a stale explanation outliving its defect. Three rules do that, all failures rather than warnings: - a not-Ready release that is NOT listed still fails the gate; - a listed release that is Ready again fails until its line is deleted; - a line naming no release in the cluster fails too. So the fastest way to remove a line is to fix the defect, and the file cannot be wrong about the present without saying so. A named file that cannot be read is also a failure: treating it as "nothing expected" would turn a typo into a gate that quietly accepts more than it claims. hack/wait-hr-ready_test.bats drives the real script through a kubectl stub over all of it. It is deliberately not named hack/e2e-*.bats: the unit target is $(filter-out hack/e2e-%.bats,...), so a file named after the script it tests would be excluded from the suite and never run. Verified non-vacuous by mutation — disabling each of the three rules in turn turns exactly one case red, and the missing-file refusal likewise. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
…its CRD (#3980) ## What breaks today Upgrading any existing cluster to a build that carries the new MongoDB backup driver wedges the `backupstrategy-controller` HelmRelease permanently, with `resource mapping not found for name: "cozy-default-mongodb" ... no matches for kind "MongoDB" in version "strategy.backups.cozystack.io/v1alpha1"; ensure CRDs are installed first`. This chart ships its `strategy.backups.cozystack.io` CRDs as ordinary templates, and Helm resolves every document in a release manifest through the cluster's RESTMapper in `Build()` **before** it applies any of them. A Strategy CR whose CRD is not served yet therefore fails `Build()`, so the release applies **zero** objects — including the CRD that would have made the mapping resolve — and every retry renders the same manifest. Nothing recovers on its own. ## Why the gate that exists does not cover it The chart already knows this trap: it is why every default Strategy gates on the bucket name. That gate covers the **first install** of a fresh cluster, where nothing is resolvable yet and no Strategy renders at all. It cannot cover shipping a **new kind** to a chart that is already installed — there the bucket name resolved releases ago, so the first upgraded revision renders the new CRD and the new Strategy CR together and wedges. ## The fix Gate the CR on its CRD actually being served, through a `crdEstablished` helper. The helper asks about the **CRD object** rather than listing the custom kind, because `lookup` on a kind the apiserver does not serve is a render *error*, not an empty result, which would trade this wedge for a different permanent failure; `apiextensions.k8s.io/v1` is always served, so that lookup can only answer yes or no. It requires `Established=True` rather than mere existence, because that is when the kind enters the RESTMapper. Convergence afterwards is the mechanism the bucket gate already relies on, and is **not** the HelmRelease interval: once the CRD is served, the controller's `DefaultObjectsGate` sees the kind mapped and the CR absent and forces a real Helm upgrade whose render includes it. Kinds that do not map at all are skipped there (`meta.IsNoMatchError`), which is what stops the gate forcing upgrades against a render that cannot yet produce the object. `bucketNameOverride` short-circuits the CRD lookup for the same reason it short-circuits the bucket lookup: it marks an offline render — `helm template`, a CI diff, a unit test — where there is no apiserver to ask. Live deploys go through Flux and never set it. ## How it was found The release upgrade E2E lane in #3276, on the `v1.6.2 → main` path. `mongodbs.strategy.backups.cozystack.io` is the only definition added to this chart since the tag, so this is reachable only by upgrading an existing install — no install-only suite can see it. ## Verification Reproduced and fixed against a live cluster that happens to be in exactly the pre-upgrade state (the seven older strategy CRDs present, `mongodbs` absent). `helm template --dry-run=server` of main's chart fails with the error above and produces zero documents; the same render with this change succeeds with 21 documents and the Strategy correctly withheld. The helper's two branches were probed the same way against that cluster: a served CRD answers `true`, an absent one answers empty. The new unit case pins the gate offline through the external-S3 path, where the bucket name resolves without the offline override so that only the CRD gate can hold the CR back. Removing the gate turns exactly that case red; the suite is 19/19 green with it. ```release-note Fixed a permanent failure of the backupstrategy-controller release when upgrading a cluster to a version that adds a new backup-strategy kind: the default MongoDB Strategy is now withheld until its CRD is served, instead of failing the whole release with "no matches for kind". ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved backup strategy rendering during upgrades by waiting until the required MongoDB resource definition is available. * Preserved offline rendering when an explicit bucket name is provided. * Prevented invalid default strategies from being generated when the required resource definition is unavailable. * **Tests** * Added coverage for offline rendering and unavailable resource-definition scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
What this PR does
Adds automated release upgrade testing — the "previous latest minor stable release → current version" path — rebuilt for the Chainsaw-based E2E suite (#2826). It installs the previous Cozystack, seeds real workloads with canary data, upgrades the platform to the build under test, then verifies workloads survived, data is intact, every HelmRelease reconciled, PVs stayed Bound, and the migration stamp advanced.
Supersedes #2401, which predates the BATS→Chainsaw migration and is now a single conflicting 895-line BATS file. Its useful pieces (previous-version resolution, the isp-full platform Package, the canary SQL, the CrashLoop/unbound-PV baseline diff) are re-expressed here.
Architecture
Chainsaw cannot pause mid-test for an external
helm upgrade, so the flow is two Chainsaw phases bracketing the upgrade, driven bymake upgrade-cozystackinside the e2e sandbox:DRY over existing files
.github/actions/e2e-{download-assets,prepare,collect,teardown}) for the sandbox lifecycle, shared by both thee2ejob and the newupgrade-e2ejob. The existinge2ejob is refactored onto them (behavior-preserving).hack/e2e-wait-hr-ready.sh— the all-HelmReleases-Ready gate, extracted frome2e-install-cozystack.batsso install and upgrade share one legible, fail-fast gate.hack/upgrade-prev-version.sh(+upgrade-prev-version_test.bats, 11 cases) — resolves the baseline version from git tags.applythe existing app manifests by relative path — no re-authored CRs.Trigger
A new
upgrade-e2ejob inpull-requests.yaml, a sibling ofe2ethat runs in parallel (no added critical-path wall-clock). It runs on the release-promotion PR (releaselabel) or any PR a maintainer opts in with the newupgrade-e2elabel. Advisory / non-blocking — its own check, gates nothing.Coverage
Always-on: PostgreSQL + MariaDB canary rows, Redis/VM volume survival, migration stamp advance, all-HRs-Ready + no-new-unbound-PV, aggregated API available. The tenant-Kubernetes survival suites are env-gated OFF (
UPGRADE_E2E_TENANT_K8S) as the flakiest surface (nested KVM + DRBD + tenant-worker image import), pending validation on a dev cluster.Status
Ready. The lane now runs end to end and has done the job it was built for.
It found a release-blocking upgrade defect. On the
v1.6.2 → mainpath thebackupstrategy-controllerrelease wedges permanently withno matches for kind "MongoDB": the chart ships its strategy CRDs as ordinary templates, Helm resolves the whole manifest through the RESTMapper before applying any of it, and the new MongoDB Strategy CR has no mapping on the upgrade that first ships its CRD — so the release applies zero objects, including the CRD that would have fixed it, and never recovers. Reachable only by upgrading an existing install, which is precisely the gap here. Fixed separately in #3980.One known failure is recorded rather than left as a permanent red.
tenant-test/vm-instance-testdoes not return to Ready across the upgrade (#3984) while passing on a fresh install in the same run.hack/e2e-chainsaw-upgrade/expected-not-readyrecords it, and the gate fails on anything unlisted, on a listed release that is Ready again, and on a line naming no release at all — so the file cannot outlive the defect. Only the upgrade lane reads it; the install gate is unchanged.The required
E2E Testscheck is red for reasons outside this PR. Touching shared dependencies escalates suite selection to the full 20-suite set, so this PR runs the whole surface that narrower PRs never touch:ingress-hostname-policyfails on a main regression reproduced on plain main in the nightly (#3983), andkubernetes-latest/kubernetes-previousare the known tenant-apiserver flake class. Neither is caused by this diff.Also in this round: the
upgrade-e2elabel is admitted to all five workflow label gates (the fifth,verify-release-candidate, would otherwise let a labelled promote PR stamp a red over a green required status); each Chainsaw phase publishes its own JUnit report, so a seed-phase failure is no longer published under the verify name; the platform verify fails instead of passing when its baseline is missing; the asset download usescurl --fail; andhack/sandbox-runner-headroom.batsnow follows a localuses:into a composite action, since moving sandbox bring-up into.github/actions/e2e-prepareput the marker it matches on inside an action — the blind spot that file's own header had named as its floor.The
upgrade-e2ejob runs only on a PR carrying theupgrade-e2eorreleaselabel. Its check isUpgrade E2E Test, which is not a required context — branch protection requires onlypre-commitandE2E Tests— so the lane is advisory and gates nothing.Costs this accepts, stated rather than left implicit: a second full platform install per run on a 32-vCPU runner with a 180-minute ceiling, doubled exposure to install-phase flakiness, and six verify suites to maintain as those apps change.
Summary by CodeRabbit
etcdAdoptSkipBackupmigration flag to bypass the pre-adoption safety snapshot.