Skip to content

[ci] Add upgrade test job to release pipeline - #2401

Closed
myasnikovdaniil wants to merge 3 commits into
mainfrom
ci/upgrade-test-job
Closed

myasnikovdaniil wants to merge 3 commits into
mainfrom
ci/upgrade-test-job

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Apr 15, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Adds automated upgrade testing that runs on every tag push as part of the release pipeline. Tests the most common upgrade path: previous latest minor release → current version.

New files

  • hack/e2e-upgrade-cozystack.bats — 15-step BATS test script covering install → workloads → upgrade → validate
  • packages/core/testing/Makefile — upgrade-cozystack target
  • .github/workflows/tags.yaml — upgrade-test job (runs in parallel with changelog generation)

How it works

  1. Provisions a 3-node Talos cluster (reuses existing e2e-prepare-cluster.bats)
  2. Determines the previous stable minor release via git tag
  3. Extracts the previous version's installer chart from the git tag (git show)
  4. Installs previous version via helm upgrade (same mechanism users use)
  5. Deploys PostgreSQL with 3 canary rows as test data
  6. Upgrades to the current version (helm upgrade installer packages/core/installer)
  7. Validates post-upgrade health:
    • All HelmReleases reach Ready=True
    • No CrashLoopBackOff pods
    • All PersistentVolumes remain Bound
    • PostgreSQL data intact (row count + value check)
    • Cozystack API available
    • Platform system pods healthy

Design decisions

  • Tests platform upgrade only (uses new Talos image, which is backwards-compatible)
  • Self-contained script — doesn't depend on e2e-install-cozystack.bats
  • Runs on oracle-vm-24cpu-96gb-x86-64 runner with 150-minute timeout
  • Always cleans up sandbox on completion/failure

Release note

Added automated upgrade testing to the release pipeline. Every tag push now tests upgrading from the previous minor release, validating HelmRelease reconciliation, pod health, data integrity, and API availability.

Summary by CodeRabbit

  • Tests
    • Added a comprehensive end-to-end upgrade test suite validating minor-version upgrades, infrastructure stability, workload/data preservation, storage behavior, and post-upgrade health checks.
  • Chores
    • New CI job runs the upgrade validation and gates changelog/website updates on its success.
  • Chores
    • Added Makefile targets to invoke the upgrade test and a configuration tweak to CI runner recognition.

Adds automated upgrade testing that runs on every tag push alongside
the existing release preparation. Tests the most common upgrade path:
previous latest minor release -> current version.

The upgrade test provisions a 3-node Talos cluster, installs the
previous stable release via its installer chart (extracted from the
git tag), deploys PostgreSQL with test data, upgrades to the current
version, and validates that all HelmReleases reconcile, no pods are
in CrashLoopBackOff, PersistentVolumes remain Bound, PostgreSQL data
survives intact, and the Cozystack API stays available.

New files:
- hack/e2e-upgrade-cozystack.bats: 15-step BATS test script
- packages/core/testing/Makefile: upgrade-cozystack target
- .github/workflows/tags.yaml: upgrade-test job (parallel with changelog)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
@coderabbitai

coderabbitai Bot commented Apr 15, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a9679f5f-053c-45c4-9fd9-364086bb3a2c

📥 Commits

Reviewing files that changed from the base of the PR and between 33322b8 and ae8bbd6.

📒 Files selected for processing (4)
  • .github/actionlint.yaml
  • .github/workflows/tags.yaml
  • hack/e2e-upgrade-cozystack.bats
  • packages/core/testing/Makefile
✅ Files skipped from review due to trivial changes (1)
  • .github/actionlint.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/testing/Makefile
  • .github/workflows/tags.yaml

📝 Walkthrough

Walkthrough

Adds a new upgrade-test job and end-to-end Bats suite that checks out the release branch, downloads the draft release asset, provisions a sandbox, runs an automated upgrade validation against a previous minor release, collects/uploaded reports, and always attempts sandbox and workspace teardown.

Changes

Cohort / File(s) Summary
GitHub Actions Workflow
.github/workflows/tags.yaml
Added jobs.upgrade-test and updated downstream job dependencies/conditions (generate-changelog, update-website-docs) to require/guard on upgrade-test success; job implements token generation, tag/version derivation, draft asset download, sandbox provisioning, upgrade execution, artifact upload, and guaranteed teardown.
End-to-end Upgrade Tests
hack/e2e-upgrade-cozystack.bats
New comprehensive Bats test suite that locates the previous minor release, installs it, establishes baseline state (storage, workloads, VMs, tenant control plane), performs platform upgrade, and validates post-upgrade stability and data/workload preservation.
Testing Makefile
packages/core/testing/Makefile
Added UPGRADE_TARGET_TAG and STRICT_BASELINE variables and new upgrade-cozystack target to run the Bats suite within the sandboxed test environment.
Actionlint Configuration
.github/actionlint.yaml
Registered a self-hosted-runner label (oracle-vm-24cpu-96gb-x86-64) for actionlint checks.

Sequence Diagram(s)

sequenceDiagram
  participant TagPush as Tag push
  participant Actions as GitHub Actions
  participant GH_API as GitHub API (gh)
  participant Runner as Self-hosted Runner / Sandbox
  participant MakeDocker as Docker / make inside sandbox
  participant Artifacts as GitHub Artifacts
  TagPush->>Actions: trigger tags workflow
  Actions->>Actions: start prepare-release
  Actions->>Actions: start upgrade-test job
  Actions->>GH_API: generate GitHub App token / fetch draft release asset
  GH_API-->>Actions: return asset (nocloud-amd64.raw.xz)
  Actions->>Runner: checkout release-<version>, copy workspace, compute SANDBOX_NAME
  Runner->>Runner: provision sandbox (make ... prepare-env) [retries up to 3]
  Runner->>MakeDocker: run upgrade test (hack/cozytest.sh hack/e2e-upgrade-cozystack.bats)
  MakeDocker-->>Runner: test results, cozyreport.tgz
  Runner->>Artifacts: upload cozyreport.tgz
  Runner->>Runner: teardown sandbox / cleanup (always)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped from tag to sandbox bright,
Downloaded assets through the night,
I ran the tests, kept data tight,
Reports packed up—what a sight! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[ci] Add upgrade test job to release pipeline' accurately and concisely summarizes the main change—introducing a new upgrade-test job to the release pipeline workflow.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/upgrade-test-job

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 and usage tips.

Four fixes surfaced by manually running the upgrade on a long-lived
cluster (dev10 → v1.3.0-rc.1):

1. Use `cozystack` as the helm release name (not `installer`) to match
   the install command documented on the website.
2. Pull the published OCI chart
   (oci://ghcr.io/cozystack/cozystack/cozy-installer --version X.Y.Z)
   instead of a local `packages/core/installer` path, so the release
   pipeline actually exercises the OCI artifact end users consume.
3. Compare pre/post snapshots in Phase 4 validation (HR-not-Ready,
   CrashLoopBackOff pods, unbound PVs) so pre-existing noise on
   non-pristine clusters doesn't mask real upgrade regressions.
4. New `Verify migration state after upgrade` test — asserts the
   cozystack-version configmap equals the chart's
   migrations.targetVersion, doesn't regress, and the migration job
   completed when a version bump was needed.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, reopen this pull request to trigger a review.

@dosubot dosubot Bot added size/XL This PR changes 500-999 lines, ignoring generated files kind/feature Categorizes issue or PR as related to a new feature labels Apr 17, 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 enhances the release process by implementing automated upgrade testing. By simulating a real-world upgrade scenario from the previous stable minor release, the new pipeline job ensures that platform upgrades do not introduce regressions in workload stability, data integrity, or system health.

Highlights

  • Automated Upgrade Testing: Introduced a new BATS test script to automate the validation of platform upgrades from the previous minor release to the current version.
  • CI Pipeline Integration: Added a new upgrade test job to the release pipeline that triggers on tag pushes, ensuring stability across upgrade paths.
  • Validation Coverage: The test suite validates HelmRelease reconciliation, pod health, PersistentVolume status, PostgreSQL data integrity, and API availability post-upgrade.

🧠 New Feature in Public Preview: You can now enable Memory 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/tags.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 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 counter productive. 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 new end-to-end upgrade test suite in hack/e2e-upgrade-cozystack.bats and a corresponding Makefile target to automate the validation of Cozystack upgrades from the previous stable minor release. The test covers workload deployment, data integrity verification for PostgreSQL, and system health checks post-upgrade. Feedback focuses on improving the robustness of the test script, including ensuring the test fails explicitly when HelmReleases do not reconcile, adding checks for empty pod variables to provide clearer error messages, refining the regex for identifying stable releases, and addressing the use of magic numbers in resource count assertions.

Comment on lines +122 to +125
if kubectl get hr -A | grep -v " True " | grep -v NAME; then
kubectl get hr -A
echo "Some HelmReleases failed to reconcile (previous version)" >&2
fi

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

The test currently prints an error message if HelmReleases fail to reconcile, but it does not actually fail the test. This could lead to false positives where the upgrade test proceeds despite a broken initial state. You should explicitly exit with a non-zero status here.

  if kubectl get hr -A | grep -v " True " | grep -v NAME; then
    kubectl get hr -A
    echo "Some HelmReleases failed to reconcile (previous version)" >&2
    exit 1
  fi

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in ae8bbd6: added a STRICT_BASELINE env switch (defaults to 0). The tags.yaml upgrade-test step sets STRICT_BASELINE=1, so Phase 1 now fails fast on reconcile errors in CI (the sandbox is always pristine). Long-lived dev clusters stay lenient.

Comment on lines +296 to +299
pg_pod=$(kubectl get pods -n tenant-test \
-l "cnpg.io/cluster=postgres-${name}" \
--field-selector=status.phase=Running \
-o jsonpath='{.items[0].metadata.name}')

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

If no running PostgreSQL pods are found, pg_pod will be empty, causing the subsequent kubectl exec to fail with an unclear error message. It is better to verify that a pod was actually found before attempting to use it.

  pg_pod=$(kubectl get pods -n tenant-test \
    -l "cnpg.io/cluster=postgres-${name}" \
    --field-selector=status.phase=Running \
    -o jsonpath='{.items[0].metadata.name}')
  [ -n "$pg_pod" ] || { echo "PostgreSQL pod not found"; exit 1; }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in ae8bbd6. Added [ -n "$pg_pod" ] || { echo "No running PostgreSQL pod found for postgres-${name}" >&2; exit 1; } right after the pod lookup.

Comment on lines +484 to +487
pg_pod=$(kubectl get pods -n tenant-test \
-l "cnpg.io/cluster=postgres-${name}" \
--field-selector=status.phase=Running \
-o jsonpath='{.items[0].metadata.name}')

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

Similar to the pre-upgrade check, if no running PostgreSQL pods are found after the upgrade, pg_pod will be empty. Adding a check here ensures the test fails with a clear message if the workload didn't recover.

  pg_pod=$(kubectl get pods -n tenant-test \
    -l "cnpg.io/cluster=postgres-${name}" \
    --field-selector=status.phase=Running \
    -o jsonpath='{.items[0].metadata.name}')
  [ -n "$pg_pod" ] || { echo "PostgreSQL pod not found after upgrade"; exit 1; }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same guard applied post-upgrade in ae8bbd6 — errors out cleanly with "No running PostgreSQL pod found for postgres-${name} after upgrade" instead of a cryptic kubectl exec failure.

Comment thread hack/e2e-upgrade-cozystack.bats Outdated
# Latest stable (non-prerelease) tag from the previous minor line
local prev_release
prev_release=$(git tag -l "v${major}.${prev_minor}.*" --sort=-v:refname \
| grep -v -E '-(rc|alpha|beta)\.' | head -1)

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 current regex for excluding pre-releases only covers rc, alpha, and beta. According to SemVer, any tag containing a hyphen is considered a pre-release. Using grep -v '-' would be more robust for identifying the latest stable release in the previous minor line.

    | grep -v '-' | head -1)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Switched to grep -v -- '-' in ae8bbd6 — any SemVer pre-release (including future -pre, -preview, custom prefixes) is now excluded correctly.

EOF

# Wait until HelmReleases appear & reconcile
timeout 180 sh -ec 'until [ $(kubectl get hr -A --no-headers 2>/dev/null | wc -l) -gt 10 ]; do sleep 1; 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 magic number 10 for the minimum number of HelmReleases might be fragile if the default package set changes or if a different variant is used. Consider making this check more dynamic or documenting why 10 is the expected minimum for the isp-full variant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added an inline comment in ae8bbd6 explaining that 10 is a conservative floor for "the operator started reconciling" — the isp-full variant produces many more HRs, and the exact count varies by variant, so a threshold is more robust than an exact match.

@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: 3

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

122-125: Minor: grep -v NAME is unanchored.

grep -v NAME would also filter any future row whose namespace or release name happens to contain the substring NAME. Anchor it to the header row (grep -vE '^NAMESPACE\s') or reuse the same awk expression used for the baseline at line 334-335 for consistency. Not a bug today since this block only emits a warning and the authoritative check is the delta comparison post-upgrade.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hack/e2e-upgrade-cozystack.bats` around lines 122 - 125, The pipeline
unanchors the header filter in the if-block that runs `kubectl get hr -A | grep
-v " True " | grep -v NAME`; replace the unanchored `grep -v NAME` with an
anchored header filter such as `grep -vE '^NAMESPACE\s'` (or reuse the same
`awk` expression used elsewhere in this script for baseline filtering) so only
the header row is excluded and namespace/release names containing "NAME" are not
accidentally filtered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/tags.yaml:
- Line 243: Add the custom runner label "oracle-vm-24cpu-96gb-x86-64" to the
actionlint configuration by appending it under the self-hosted-runner.labels key
in the repo's actionlint config so actionlint recognizes this self-hosted runner
label (ensure the label string exactly matches the one used in the workflow
runs-on field and save the change so CI lint jobs stop flagging it as unknown).

In `@hack/e2e-upgrade-cozystack.bats`:
- Around line 364-369: The helm upgrade command uses a too-short --timeout 2m
which can cause flakes when the pre-upgrade Job (cozystack-migration-hook) with
backoffLimit: 3 runs; update the helm upgrade invocation that calls "helm
upgrade cozystack ... --wait --timeout 2m" to use a longer timeout (e.g.
--timeout 10m) to match the post-upgrade HR wait and avoid spurious failures.
- Around line 341-343: The fallback for writing pre-cozystack-version doesn't
handle the case where kubectl exits 0 but returns empty stdout; change the logic
around the kubectl call that writes "$STATE_DIR/pre-cozystack-version" so an
empty result is treated as "0" as well (e.g., capture the kubectl output into a
variable or test for -z and write "0"), ensuring the file never ends up empty
before the integer comparisons that use that file; update the block with the
kubectl get cm ... jsonpath command and references to
"$STATE_DIR/pre-cozystack-version" accordingly.

---

Nitpick comments:
In `@hack/e2e-upgrade-cozystack.bats`:
- Around line 122-125: The pipeline unanchors the header filter in the if-block
that runs `kubectl get hr -A | grep -v " True " | grep -v NAME`; replace the
unanchored `grep -v NAME` with an anchored header filter such as `grep -vE
'^NAMESPACE\s'` (or reuse the same `awk` expression used elsewhere in this
script for baseline filtering) so only the header row is excluded and
namespace/release names containing "NAME" are not accidentally filtered.
🪄 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: bb422ae2-4d0e-4a61-b2f7-1dbfba6e2311

📥 Commits

Reviewing files that changed from the base of the PR and between ad52992 and 33322b8.

📒 Files selected for processing (3)
  • .github/workflows/tags.yaml
  • hack/e2e-upgrade-cozystack.bats
  • packages/core/testing/Makefile

Comment thread .github/workflows/tags.yaml
Comment thread hack/e2e-upgrade-cozystack.bats Outdated
Comment thread hack/e2e-upgrade-cozystack.bats Outdated

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.

Feature works as described and scope is clean. Requesting changes on four business-level points below before merge, plus closing out the outstanding bot findings.

Note: CodeRabbit's --timeout 2m concern about the migration hook is misplaced — the pre-upgrade hook lives in the platform chart, not cozy-installer, so helm upgrade cozy-installer doesn't wait on it. The other bot findings (empty pg_pod guard, pre-release regex, empty configmap value, actionlint label) are valid and should be addressed.

Comment thread hack/e2e-upgrade-cozystack.bats Outdated
# Latest stable (non-prerelease) tag from the previous minor line
local prev_release
prev_release=$(git tag -l "v${major}.${prev_minor}.*" --sort=-v:refname \
| grep -v -E '-(rc|alpha|beta)\.' | head -1)

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.

Only minor → current is covered, patch → patch is not.

For tags on a maintenance branch (e.g. v0.43.2 on release-0.43), this resolves the baseline to v0.42.latest instead of v0.43.1. The real production upgrade path a user runs on a hotfix (v0.43.1 → v0.43.2) is never exercised.

Either extend find_previous_release to prefer the previous patch on the same minor when the tag sits on a release-X.Y branch, or document this limitation in the PR body / a comment at the top of the file so reviewers know what's covered.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Decided not to exercise patch→patch here. Patch releases are by policy scoped to bug fixes with no schema or API changes — the interesting failure modes land on minor→current upgrades where new features and migration hooks show up. We can revisit if a patch ever ships something breaking, but I'd rather not pay the test-matrix cost for paths we deliberately keep low-risk.

console.log(`PR already exists from ${head} to ${base}`);
}

upgrade-test:

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.

A failed upgrade-test does not gate release publication.

prepare-release creates the draft release, uploads assets, tags the api submodule, and opens the release PR BEFORE this job runs. generate-changelog and update-website-docs declare needs: [prepare-release], not needs: [upgrade-test], so they proceed regardless of the upgrade-test outcome. A human must remember to check this job's status before promoting the draft.

That may be intentional (advisory gate), but it's worth either wiring generate-changelog / update-website-docs to also depend on upgrade-test, or calling out the advisory-by-design behavior in the PR body.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point — wired it up in ae8bbd6. generate-changelog and update-website-docs now have upgrade-test in their needs: and && needs.upgrade-test.result == 'success' in their if:. A failing upgrade-test leaves the draft release in place and skips changelog/website PRs, so a human decides whether to promote.

# Phase 2: Deploy workloads and seed test data
# ===================================================================

@test "Deploy PostgreSQL with test data" {

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.

Only PostgreSQL is exercised.

The PR body says "representative workloads" but only one workload type is covered. An upgrade that silently breaks VMs, Kubernetes-in-Kubernetes, or other managed apps would still pass this test. Fine as a first iteration; worth stating explicitly in the PR body that broader workload coverage is a follow-up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Broadened in ae8bbd6: Phase 2 now also deploys MariaDB (seed+verify 3 rows, mirrors the PostgreSQL canary), Kafka (cluster+topic with UID check), VMInstance (UID + phase=Running check post-upgrade), and a tenant Kubernetes cluster via Kamaji (TenantControlPlane Ready + UID). PostgreSQL stays the deep data-integrity canary; the others verify the CRD/operator path survives the upgrade without drift.

Comment thread hack/e2e-upgrade-cozystack.bats Outdated

if kubectl get hr -A | grep -v " True " | grep -v NAME; then
kubectl get hr -A
echo "Some HelmReleases failed to reconcile (previous version)" >&2

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.

The warning-only behavior was justified in commit 2 as "tolerate pre-existing noise on long-lived clusters", but in CI the cluster is always pristine — a broken baseline there should fail fast, not proceed silently into Phase 2.

Consider an env switch (e.g. STRICT_BASELINE=1) so the CI run is strict while dev-cluster runs stay lenient.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Added STRICT_BASELINE env switch in ae8bbd6 — defaults to 0 (lenient on dev clusters), and tags.yaml passes STRICT_BASELINE=1 to the CI run so a broken baseline fails fast there. Same thread as the Gemini comment above.

Broaden workload coverage, tighten correctness, and wire the release
gate so a failed upgrade-test actually blocks publication.

* Phase 2 / Phase 4: add MariaDB (seed+verify rows), Kafka (cluster +
  topic survival via UID check), VMInstance (UID + phase=Running),
  and tenant Kubernetes cluster (TenantControlPlane Ready + UID).
  Keeps PostgreSQL data integrity as the deep canary.
* STRICT_BASELINE env switch: Phase 1 reconcile check fails fast in
  CI (always pristine), stays lenient on long-lived dev clusters.
  tags.yaml sets STRICT_BASELINE=1.
* Release gate: generate-changelog and update-website-docs now depend
  on upgrade-test. A failing job leaves the draft release unpromoted.
* Correctness: empty-pod guards before kubectl exec, SemVer-correct
  pre-release filter (grep -v -- '-'), empty-stdout coalesce for the
  pre-upgrade cozystack-version read, 2m -> 10m upgrade timeout to
  cover the cozystack-migration-hook pre-upgrade Job.
* Lint: register the oracle-vm-24cpu-96gb-x86-64 self-hosted runner
  label in .github/actionlint.yaml so actionlint stops flagging it.

Signed-off-by: Myasnikov Daniil <[email protected]>

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.

Prior four points (STRICT_BASELINE, release-pipeline gating, broader workload coverage, patch-to-patch scoping) are all addressed in ae8bbd6 — thanks for the turnaround.

Two new blockers on HEAD:

  1. The four comm -23 <(...) <(...) constructs in the bats file will fail under dash, which is the sandbox's /bin/sh. Reproduced — cozytest.sh sources the file and dash syntax-errors before the first test runs.
  2. The asset-download curl lacks --fail, so any 4xx/5xx writes an HTML error body into the Talos image file.

Because generate-changelog and update-website-docs now depend on upgrade-test.result == 'success', a failing upgrade-test blocks release promotion entirely — worth fixing both before merge rather than after the first tag push.

Non-blocking observations inline.

while [ $elapsed -lt $max_wait ]; do
local new_not_ready
new_not_ready=$(comm -23 \
<(kubectl get hr -A --no-headers 2>/dev/null | awk '$4 != "True" {print $1"/"$2}' | sort) \

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.

Process substitution <(kubectl ...) is bash-only. hack/cozytest.sh has #!/bin/sh and sources the transformed bats file via . "$TMP_SH" (line 104). The sandbox image (packages/core/testing/images/e2e-sandbox/Dockerfile, FROM ubuntu:22.04) has /bin/sh -> dash, and dash parses the whole file at source time — it syntax-errors on the <(...) lines and no test ever runs. Reproduced in a vanilla ubuntu:22.04 container:

sh: 3: /tmp/t.sh: Syntax error: "(" unexpected

Same issue affects lines 673-674, 710-711, 725-726.

Because generate-changelog and update-website-docs now carry needs.upgrade-test.result == 'success', a permanently-failing upgrade-test blocks the whole release pipeline on every tag.

Two fixes:

  • Switch hack/cozytest.sh shebang to #!/bin/bash (minimal).
  • Or rewrite the four blocks using temp files, e.g.:
    kubectl get hr -A --no-headers 2>/dev/null \
      | awk '$4 != "True" {print $1"/"$2}' | sort > "$STATE_DIR/cur-hr-not-ready"
    new_not_ready=$(comm -23 "$STATE_DIR/cur-hr-not-ready" "$pre_not_ready")

echo "nocloud-amd64.raw.xz not found in draft release for ${RELEASE_TAG}" >&2
exit 1
fi
curl -sSL \

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.

curl -sSL -o _out/assets/nocloud-amd64.raw.xz "$ASSET_URL" returns exit 0 on 4xx/5xx and writes the error body into the output file. The upgrade test then consumes a corrupted image and fails downstream with an unrelated-looking error. Add --fail --retry 3 --retry-delay 5 so the job fails fast with a clear signal.

pg_pod=$(kubectl get pods -n tenant-test \
-l "cnpg.io/cluster=postgres-${name}" \
--field-selector=status.phase=Running \
-o jsonpath='{.items[0].metadata.name}')

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.

Observation (non-blocking): with replicas: 2 the selector cnpg.io/cluster=postgres-${name} returns any CNPG pod, and psql -U postgres over the local socket talks to whatever instance landed in .items[0]. If that's a standby, the INSERT fails with cannot execute INSERT in a read-only transaction. The CI data point is small so the flake may look rare, but it's path-dependent and will surface eventually. Pin to the primary: -l "cnpg.io/cluster=postgres-${name},cnpg.io/instanceRole=primary".

contents: write
pull-requests: write
if: needs.prepare-release.result == 'success'
if: needs.prepare-release.result == 'success' && needs.upgrade-test.result == 'success'

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.

Observation (non-blocking): on the skip path (prepare-release.outputs.skip == 'true'), upgrade-test's if: causes it to be skipped, so its result is not 'success', and generate-changelog (and by the same logic update-website-docs) now also skips. The pre-PR behavior was to still generate the changelog on re-runs of already-published releases. If that regression is intentional, fine; otherwise add an OR branch like (needs.prepare-release.outputs.skip == 'true' || needs.upgrade-test.result == 'success').

@test "Deploy tenant Kubernetes cluster" {
local name='upgrade-k8s'
local k8s_version
k8s_version=$(yq 'keys | sort_by(.) | .[-1]' packages/apps/kubernetes/files/versions.yaml)

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.

Observation (non-blocking): yq 'keys | sort_by(.) | .[-1]' is a lexicographic sort, not a semver sort. Today it produces the correct answer because every key is v1.30-v1.35 (two-digit minor). A future v1.9 or v1.100 entry would silently pick the wrong version. Split on . and sort numerically, or post-process with sort -V after stripping the v.

@test "Verify platform pods are healthy after upgrade" {
# Check that key system namespaces have no unhealthy pods
local failed=false
for ns in cozy-system cozy-fluxcd cozy-linstor cozy-metallb; 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.

Observation (non-blocking): the for ns in cozy-system cozy-fluxcd cozy-linstor cozy-metallb whitelist predates the expanded workload coverage. With MariaDB/Kafka/VMInstance/Kamaji now in the test, isp-full also has cozy-kubevirt, cozy-kamaji, cozy-kafka-operator, cozy-cluster-api, cozy-monitoring, etc. — pod regressions in those are invisible to this assertion. Iterate over kubectl get ns -o name | grep '^namespace/cozy-' instead.

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.

NOT LGTM — head is unchanged since my previous review (ae8bbd6), so the two blockers I raised then are still open. A closer pass on the new release gating and the post-upgrade check surfaced three more. The prior four business points (STRICT_BASELINE, release-pipeline gating, broader workload coverage, patch-to-patch scoping) and the earlier bot findings (empty pg_pod guard, pre-release regex, empty-configmap coalesce, actionlint label) are all addressed.

Status of my prior CHANGES_REQUESTED: both blockers still open (B1, B2). The non-blocking inline observations from that review are also still present (carried below).

Blockers

B1 — Process substitution <(...) aborts the whole test file under dash.

File: hack/e2e-upgrade-cozystack.bats:657-658, 673-674, 710-711, 725-726

Evidence: hack/cozytest.sh is #!/bin/sh (line 1), runs set -eu (line 6), and sources the converted test file via . "$TMP_SH" (line 104). The sandbox image is FROM ubuntu:22.04 where /bin/sh is dash. dash parses the entire sourced file — including these comm -23 <(...) constructs inside the @test function bodies — at source time and syntax-errors (Syntax error: "(" unexpected) before a single test runs.

Impact: upgrade-test fails on every tag; with the new gating, generate-changelog and update-website-docs are then permanently blocked.

Fix: replace each process substitution with temp files (... | sort > "$STATE_DIR/cur", then comm -23 "$STATE_DIR/cur" "$pre"), or switch hack/cozytest.sh to #!/bin/bash.

B2 — Asset download lacks --fail; a 4xx/5xx body is written into the image file.

File: .github/workflows/tags.yaml:291-295

Evidence: curl -sSL -H ... -o _out/assets/nocloud-amd64.raw.xz "$ASSET_URL" — -sSL does not fail on HTTP errors, so an API 4xx/5xx returns exit 0 and writes the error body into the output file.

Impact: a corrupted Talos image is consumed downstream and the job fails later with an unrelated-looking error instead of failing fast at the download.

Fix: add --fail --retry 3 --retry-delay 5.

B3 — Post-upgrade HelmRelease check passes vacuously.

File: hack/e2e-upgrade-cozystack.bats:645-682

Evidence: helm upgrade cozy-installer --wait blocks only on the installer chart (operator + the platform PackageSource), not on the platform HelmReleases, which Flux re-reconciles asynchronously from the new source. The loop computes new_not_ready = (HRs not Ready now) − (pre-upgrade not-ready baseline) and breaks the instant that set is empty. In the window after helm upgrade returns but before Flux marks anything not-ready, every HR still reads Ready=True from the old reconcile, so after the single sleep 10 (line 647) the loop prints "No HelmReleases newly not-ready after 0s" and passes — without any HR having re-applied the new release. pre-hr-count (line 585) is captured but never compared.

Impact: the test's central claim ("all HelmReleases reconcile after upgrade") is not actually verified; a broken upgrade can pass green.

Fix: require a positive rollout signal before the not-ready delta — assert the PackageSource digest advanced and was observed, or that HR lastAppliedRevision / chart version moved to the new version, before entering the loop.

B4 — First-of-major (vX.0.0) and missing-previous-stable hard-fail the job and block release docs.

File: hack/e2e-upgrade-cozystack.bats:36-39, 49-52

Evidence: find_previous_release returns 1 when minor == 0 or no stable previous-minor tag exists. hack/cozytest.sh injects set -e at the top of every @test body (line 91), so PREV_RELEASE=$(find_previous_release) in the first test aborts with exit 1 → the job fails.

Impact: the first release of a new major has no upgrade path to test yet would block its own generate-changelog / update-website-docs. This is an expected condition, not a regression.

Fix: detect these cases and skip gracefully (exit 0 with a clear "no previous release, nothing to upgrade" message) instead of failing.

B5 — Gating regression on already-published tags.

File: .github/workflows/tags.yaml (generate-changelog / update-website-docs needs + if)

Evidence: upgrade-test carries if: needs.prepare-release.outputs.skip != 'true', so on a re-pushed/already-published tag it is skipped → result == 'skipped'. generate-changelog now requires needs.upgrade-test.result == 'success', and 'skipped' != 'success', so the changelog — previously gated only on needs.prepare-release.result == 'success' — no longer runs on the skip path.

Impact: re-running an already-published release no longer regenerates the changelog/website docs.

Fix: gate on needs.upgrade-test.result != 'failure', or add an explicit needs.prepare-release.outputs.skip == 'true' OR-branch.

Non-blocking

  1. PostgreSQL seed can hit a standby. hack/e2e-upgrade-cozystack.bats:305-309 — with replicas: 2, -l cnpg.io/cluster=postgres-${name} + .items[0] can select a standby; the local-socket INSERT then fails read-only. Pin to the primary (,cnpg.io/instanceRole=primary) or write through the -rw service. Same applies post-upgrade at 745-750.
  2. Lexicographic version pick. bats:511 — yq 'keys | sort_by(.) | .[-1]' is lexicographic; correct only while every key is a two-digit minor (v1.30-v1.35). A future v1.9 / v1.100 would pick wrong. Sort numerically or sort -V.
  3. Stale namespace whitelist. bats:879 — the cozy-system cozy-fluxcd cozy-linstor cozy-metallb list predates the expanded workloads; pod regressions in cozy-kubevirt / cozy-kamaji / cozy-kafka-operator / etc. are invisible. Iterate over kubectl get ns -o name | grep '^namespace/cozy-'.
  4. Convention drift vs docs/agents/e2e-testing.md. Bare sleep 5 / sleep 10 precede several kubectl waits (bats:297, 373, 444, 647, 707) without the mandated until kubectl get …; do sleep 2; done existence backstop or a TODO(e2e-replace-fixed-timeouts): justification. That doc landed after this branch was cut; reconcile on rebase.
  5. No remediation guard. The post-upgrade check does not inspect .status.history, so a parent HR that uninstalled+reinstalled during the upgrade but ended Ready is not caught (docs/agents/e2e-testing.md §6).
  6. Post-upgrade version read lacks a fallback. bats:688 — actual=$(kubectl get cm cozystack-version …) has no :-0 / || true (unlike the pre-upgrade capture at 602); an absent CM yields a cryptic "integer expression expected" at [ "$actual" -ge "$previous" ].
  7. Stale base. Head is far behind main and currently conflicts; main now also carries a full-suite release-e2e.yaml on tags. Rebase, then decide whether this upgrade job should share infra with release-e2e.yaml rather than provision a second Talos cluster per tag.

myasnikovdaniil added a commit that referenced this pull request Jul 15, 2026
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]>
myasnikovdaniil added a commit that referenced this pull request Jul 16, 2026
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]>
myasnikovdaniil added a commit that referenced this pull request Jul 20, 2026
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]>
@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

Superseded by #3276

myasnikovdaniil added a commit that referenced this pull request Jul 29, 2026
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]>
myasnikovdaniil added a commit that referenced this pull request Aug 15, 2026
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]>
myasnikovdaniil added a commit that referenced this pull request Aug 25, 2026
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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature Categorizes issue or PR as related to a new feature size/XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants