Skip to content

test(e2e): add chainsaw-native release upgrade testing lane - #3276

Open
myasnikovdaniil wants to merge 19 commits into
mainfrom
feat/upgrade-e2e-chainsaw
Open

myasnikovdaniil wants to merge 19 commits into
mainfrom
feat/upgrade-e2e-chainsaw

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Jul 13, 2026 •

Copy link
Copy Markdown
Contributor

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 by make upgrade-cozystack inside the e2e sandbox:

install-previous (bats, previous OCI chart)
  → seed   (chainsaw, cleanup.skipDelete=true — workloads + canary survive the phase boundary)
    → helm upgrade to the in-tree chart (bats) + all-HR-Ready gate + migration-stamp assert
      → verify (chainsaw, assert-only — survival + data integrity + platform health)

DRY over existing files

  • 4 composite actions (.github/actions/e2e-{download-assets,prepare,collect,teardown}) for the sandbox lifecycle, shared by both the e2e job and the new upgrade-e2e job. The existing e2e job is refactored onto them (behavior-preserving).
  • hack/e2e-wait-hr-ready.sh — the all-HelmReleases-Ready gate, extracted from e2e-install-cozystack.bats so 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.
  • Seed suites apply the existing app manifests by relative path — no re-authored CRs.

Trigger

A new upgrade-e2e job in pull-requests.yaml, a sibling of e2e that runs in parallel (no added critical-path wall-clock). It runs on the release-promotion PR (release label) or any PR a maintainer opts in with the new upgrade-e2e label. 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 → main path the backupstrategy-controller release wedges permanently with no 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-test does not return to Ready across the upgrade (#3984) while passing on a fresh install in the same run. hack/e2e-chainsaw-upgrade/expected-not-ready records 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 Tests check 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-policy fails on a main regression reproduced on plain main in the nightly (#3983), and kubernetes-latest / kubernetes-previous are the known tenant-apiserver flake class. Neither is caused by this diff.

Also in this round: the upgrade-e2e label 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 uses curl --fail; and hack/sandbox-runner-headroom.bats now follows a local uses: into a composite action, since moving sandbox bring-up into .github/actions/e2e-prepare put the marker it matches on inside an action — the blind spot that file's own header had named as its floor.

The upgrade-e2e job runs only on a PR carrying the upgrade-e2e or release label. Its check is Upgrade E2E Test, which is not a required context — branch protection requires only pre-commit and E2E 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.

Added automated release upgrade testing (previous minor stable → current) as a parallel, opt-in E2E lane: it installs the previous release, seeds workloads with canary data, upgrades the platform, and verifies workload survival, data integrity, HelmRelease reconciliation, and migration completion.

Summary by CodeRabbit

  • New Features
    • Added an “upgrade E2E” lane (CI label) with baseline install, seeded data, upgrade apply, and post-upgrade verification, including optional tenant Kubernetes coverage.
    • Introduced shared composite actions to stage E2E assets, prepare the sandbox with retries, always collect debug artifacts, and teardown consistently.
    • Added automatic selection of the previous stable baseline for upgrade runs.
    • Added an opt-in etcdAdoptSkipBackup migration flag to bypass the pre-adoption safety snapshot.
  • Bug Fixes
    • Improved HelmRelease readiness gates with clearer failure diagnostics.
  • Documentation
    • Documented the new upgrade lane phases and CI placement.

@github-actions github-actions Bot added area/testing Issues or PRs related to testing (e2e, bats, unit tests) size/XXL This PR changes 1000+ lines, ignoring generated files labels Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds 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.

Changes

Release upgrade E2E

Layer / File(s) Summary
Shared sandbox actions and CI wiring
.github/actions/e2e-*, .github/workflows/pull-requests.yaml, .github/labels.yml
Centralizes asset downloads, sandbox preparation, diagnostics collection, teardown, and the upgrade-e2e workflow lane.
Upgrade orchestration and version gates
hack/e2e-upgrade-*.bats, hack/e2e-wait-hr-ready.sh, hack/upgrade-prev-version*, packages/core/testing/Makefile, packages/core/platform/{values.yaml,templates/migration-hook.yaml}
Adds previous-version selection, baseline installation, upgrade execution, readiness and migration gates, Make targets, etcd adoption configuration, and resolver tests.
Upgrade seed suite
hack/e2e-chainsaw-upgrade/.chainsaw.yaml, hack/e2e-chainsaw-upgrade/seed/*
Seeds MariaDB, PostgreSQL, Redis, tenant Kubernetes, and VM resources, and records baseline storage and cluster snapshots.
Upgrade verification suite and documentation
hack/e2e-chainsaw-upgrade/verify/*, docs/agents/e2e-testing.md
Verifies platform readiness, application data, PVC-to-PV bindings, VM state, tenant Kubernetes state, and documents the release-lane flow.

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
Loading

Possibly related PRs

Suggested labels: area/uncategorized

Suggested reviewers: androndo, ivanhunters, kvaps, lexfrei

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: adding a Chainsaw-native release upgrade E2E testing lane.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/upgrade-e2e-chainsaw

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@myasnikovdaniil myasnikovdaniil added the upgrade-e2e Run the release upgrade E2E test (previous minor stable -> this build) on this PR label Jul 13, 2026
@myasnikovdaniil
myasnikovdaniil marked this pull request as ready for review July 13, 2026 11:38
@dosubot dosubot Bot added area/ci Issues or PRs related to CI workflows, GitHub Actions, automation kind/feature Categorizes issue or PR as related to a new feature labels Jul 13, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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

  • Automated Release Upgrade Testing: Added a new E2E lane that validates the upgrade path from the previous stable minor release to the current build.
  • Chainsaw Integration: Leveraged Chainsaw for seeding canary data and verifying post-upgrade state, while using BATS for orchestration.
  • Shared Infrastructure: Refactored sandbox lifecycle management into four reusable composite GitHub Actions to improve maintainability.
  • New CI Job: Introduced an upgrade-e2e job that runs in parallel to the standard E2E suite, triggered by specific labels.
  • Robust Health Checks: Extracted the HelmRelease readiness gate into a shared script to ensure consistent and fail-fast validation across both install and upgrade paths.
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
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/pull-requests.yaml
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

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.

Comment on lines +50 to +52
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}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

low

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" || {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

low

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' || {

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 win

Add scoped diagnostics for the tenant-cluster resources.

Unlike the sibling seed suites (mariadb/postgres/redis/vm), which each attach a describe + podLogs for their specific resource in catch:, this test only has events: {}. 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 a describe of the KamajiControlPlane/TenantControlPlane/MachineDeployment on 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

📥 Commits

Reviewing files that changed from the base of the PR and between eea8b73 and 6f987df.

📒 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.yaml
  • docs/agents/e2e-testing.md
  • hack/e2e-chainsaw-upgrade/.chainsaw.yaml
  • hack/e2e-chainsaw-upgrade/seed/mariadb/chainsaw-test.yaml
  • hack/e2e-chainsaw-upgrade/seed/postgres/chainsaw-test.yaml
  • hack/e2e-chainsaw-upgrade/seed/redis/chainsaw-test.yaml
  • hack/e2e-chainsaw-upgrade/seed/tenant-k8s/chainsaw-test.yaml
  • hack/e2e-chainsaw-upgrade/seed/vm/chainsaw-test.yaml
  • hack/e2e-chainsaw-upgrade/seed/zz-baseline/chainsaw-test.yaml
  • hack/e2e-chainsaw-upgrade/verify/mariadb/chainsaw-test.yaml
  • hack/e2e-chainsaw-upgrade/verify/platform/chainsaw-test.yaml
  • hack/e2e-chainsaw-upgrade/verify/postgres/chainsaw-test.yaml
  • hack/e2e-chainsaw-upgrade/verify/redis/chainsaw-test.yaml
  • hack/e2e-chainsaw-upgrade/verify/tenant-k8s/chainsaw-test.yaml
  • hack/e2e-chainsaw-upgrade/verify/vm/chainsaw-test.yaml
  • hack/e2e-install-cozystack.bats
  • hack/e2e-upgrade-apply.bats
  • hack/e2e-upgrade-install-previous.bats
  • hack/e2e-wait-hr-ready.sh
  • hack/upgrade-prev-version.sh
  • hack/upgrade-prev-version_test.bats
  • packages/core/testing/Makefile

Comment thread .github/actions/e2e-prepare/action.yaml Outdated
Comment thread hack/e2e-chainsaw-upgrade/verify/platform/chainsaw-test.yaml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6f987df and 2cb1845.

📒 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

Comment thread .github/actions/e2e-prepare/action.yaml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 win

Add existence backstops before kubectl wait and avoid ad-hoc retries.

The kubectl wait on line 69 is missing a prior existence backstop, making it vulnerable to race conditions if the Deployment is not yet created. Furthermore, lines 72-73 use an ad-hoc until kubectl wait retry loop instead of the established backstop pattern.

As per coding guidelines for E2E scripts and learnings, every kubectl wait must 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2cb1845 and a14cff5.

📒 Files selected for processing (1)
  • hack/e2e-upgrade-install-previous.bats

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
hack/e2e-upgrade-install-previous.bats (1)

72-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align with the established existence backstop pattern.

The learning explicitly advises using until kubectl get to poll for resource existence prior to a single kubectl wait call, rather than wrapping kubectl wait inside 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

📥 Commits

Reviewing files that changed from the base of the PR and between d8f3ad6 and 7ee079e.

📒 Files selected for processing (4)
  • docs/agents/e2e-testing.md
  • hack/e2e-upgrade-install-previous.bats
  • packages/core/platform/templates/migration-hook.yaml
  • packages/core/platform/values.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/agents/e2e-testing.md

IvanHunters
IvanHunters previously approved these changes Jul 20, 2026

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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 2c6b9140 already 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.etcdAdoptSkipBackup toggle could not be executed: helm template packages/core/platform fails offline on a pre-existing unconditional lookup+fail in repository.yaml (reproduces identically on merge-base, not touched by this PR), and migration-hook.yaml is itself lookup-gated. Residual risk judged low — the consumed ETCD_ADOPT_SKIP_BACKUP env var is pre-existing, already unit-tested, and the wiring is a single unconditional-else block with no cross-reference or dependsOn.
  • The chart_lint render_error + 2 missing_refs flagged by tooling are all confirmed pre-existing and unrelated (reproduced on merge-base; referencing templates untouched).
  • Both new #!/bin/sh scripts 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.

@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

Aleksei Sviridkin (@lexfrei) both blockers are fixed, plus the corrected resolver finding from your third round — db0b26b66, 4c818b2a8, 6c787399f.

B1, contract test. hack/promote-gate-contract.bats now matches the broadened expressions in plan and resolve_assets, and a new case pins the upgrade-e2e opt-in end to end: admitted by the labeled allow-list in both jobs, 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. Verified non-vacuous — narrowing the plan gate back to the full-e2e-only literal turns exactly one case red. Unit & controller tests is green again and the lane executed at this head instead of skipping.

This one was mine to own: I ran the unit suite before writing that commit rather than after, and pre-commit does not cover bats-unit-tests, so it shipped behind a "green locally" claim that did not cover the change that broke it.

B2, MariaDB header. Rewritten to -h mariadb-test-primary, matching the code at 65/70 and the step comment, so the header no longer prescribes the loopback path the same file documents as OOM-killing the server container.

Resolver, per your correction. Reproduced — v1 returns v1.0.9 with rc=0 while v1.6 is fine. Root cause is cut -f2 printing the whole field when the line carries no delimiter, so min came back as 1 and v1 was read as v1.1. Fixed with -s. Worth flagging that my first test for it was vacuous: _fixture ships no v1.0.x tag, so the buggy path errored for the wrong reason and the case passed either way — reverting -s left all eleven green. It now carries its own tag set containing v1.0.9, and reverting -s fails it and only it.

Body. ## Status rewritten — "Draft" and "unit-tests green locally" are gone, the advisory-lane reasoning and required-context check are explicit, the findings are split two-confirmed/one-unproven per your framing, and the costs you listed are named.

The red e2e is not this PR. kubernetes-latest and kubernetes-previous fail identically on #3276, #3262, #3456 and #3294, and today's nightly on main is 43/43 green including both. Same mechanism everywhere — node-join failed: fewer than 2 tenant nodes Ready, tenant cilium never installing, dependents cascading. That points at the PR e2e lane rather than any PR's content; the structural difference from nightly is that PR runs reconstruct their image set via pr.patch plus hack/overlay-main-images.sh. #3471 happens to rework exactly that file and has a fresh run in flight, which should be informative. Flagging rather than asserting — I have not confirmed the cause.

Still open from your non-blocking list: curl --fail in e2e-download-assets, the comm -13 ... || true baseline guard in verify/platform, and the dot-anchored case name that still overclaims. Happy to take those here or separately.

myasnikovdaniil and others added 15 commits August 25, 2026 15:19
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]>
@myasnikovdaniil
myasnikovdaniil force-pushed the feat/upgrade-e2e-chainsaw branch from 48ae5be to fe40b2b Compare August 25, 2026 10:50
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]>
Andrey Kolkov (androndo) added a commit that referenced this pull request Sep 9, 2026
…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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci Issues or PRs related to CI workflows, GitHub Actions, automation area/testing Issues or PRs related to testing (e2e, bats, unit tests) kind/feature Categorizes issue or PR as related to a new feature size/XXL This PR changes 1000+ lines, ignoring generated files upgrade-e2e Run the release upgrade E2E test (previous minor stable -> this build) on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants