Skip to content

fix(seaweedfs): close the 4.31 rename fallout on the 1.5.x→1.6 upgrade path - #3339

Merged
myasnikovdaniil merged 12 commits into
mainfrom
fix/seaweedfs-rename-fallout
Jul 20, 2026
Merged

fix(seaweedfs): close the 4.31 rename fallout on the 1.5.x→1.6 upgrade path#3339
myasnikovdaniil merged 12 commits into
mainfrom
fix/seaweedfs-rename-fallout

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Cozystack v1.5.0 bumped the vendored SeaweedFS chart 4.0.405 → 4.31.0, which renamed every workload after the Helm release (<name>-system-*). StatefulSet names are immutable, so upgrades through 1.5.x stood up a second, empty set beside the running one instead of renaming. #3282 pinned fullnameOverride: seaweedfs so 1.6 adopts running workloads in place — this PR closes the remaining holes on that adoption path, found by driving a disposable 3-node cluster through a real v1.4.5 → v1.5.3 → main upgrade with SeaweedFS tenants planted in every reachable state.

  • The naming guard moves into packages/system/seaweedfs — the render a platform upgrade actually re-renders: the <name>-system HelmRelease pulls this chart from a platform-managed ExternalArtifact, so the previous guard in extra/seaweedfs was never in the path. Upgrading a fresh-1.5.x tenant therefore created an empty chart-named set beside its live data and flipped the guard's own legacy-data signal. extra/ keeps a sibling copy for operator visibility; a bats parity suite pins the two detection blocks byte-identical.
  • The guard refuses instead of guessing when both naming generations exist. Nothing durable distinguishes a duplicate that never served from one that served and crashed: claim timestamps invert during the recovery runbook's own re-bind, readyReplicas: 0 is a snapshot, and Helm birth order answers "which generation is original", not "is the other one empty". Exactly one generation present is decidable, and the render proceeds (or refuses as class S) on its own.
  • Cluster-scoped RBAC is named per namespace again. 4.31 named four cluster-scoped objects after the release — identical for every tenant — so all tenants collided on one ClusterRole/ClusterRoleBinding and only the last-reconciled tenant's COSI provisioner kept its RBAC. Names return to global.seaweedfs.serviceAccountName; three of four are byte-identical to pre-4.31 and adopt in place.
  • The seaweedfs-db hand-over runs for every instance name. Migration 43 compared the owning release against the literal seaweedfs-system, so an instance named foo was skipped and its CNPG Cluster — the filer metadata for every object in that tenant's S3 — was pruned on the next reconcile, PVC included. The comparison now matches the -system suffix (shared lib/seaweedfs-db-adopt.sh), and new migration 53 re-runs the hand-over for clusters already past 43, before anything re-renders.
  • hack/seaweedfs-naming-audit.sh + docs/operations/seaweedfs-431-rename-recovery.md — what the guard's refusal points operators at: read-only classification (L / S / MIXED, naming the candidate duplicate from relative PV vintage, never a clock window) and the recovery procedures.

Scope. The supported SeaweedFS deployment is the tenant module, which hardcodes the instance name (packages/apps/tenant/templates/seaweedfs.yaml); a tenant only enables or disables it. The runbook and its selectors are scoped to that name; instances created directly against the API under other names are classified by the audit but routed to escalation. Zone/pool keys of ~40 or more characters fall outside the guard's reconstruction — an accepted limit, recorded in _naming.tpl and the runbook.

Upgrade impact. 1.4.x → 1.6: no manual steps — one generation, adopted in place. 1.5.x → 1.6 with SeaweedFS: migration 53 protects every seaweedfs-db first; then the upgrade refuses for any tenant holding both naming generations until the operator resolves the duplicate. This is deliberate — which generation holds the data is not decidable from inside a render, and guessing wrong destroys it. Release notes should present the refusal as expected behavior.

Testing. 55 chart unit tests across both packages (including new MultiZone zone-component guard cases — the suite previously had no zone shapes), 11 audit bats, 8 guard-parity bats, migration bats. Validated end-to-end on a disposable 3-node cluster driven v1.4.5 → v1.5.3 → main with five tenants covering: never-saw-4.31 (renders untouched), fresh-1.5.x (refused as class S), wedged duplicate, split duplicate, and a non-default-named instance (its database survives only with this fix). The audit classifies all five correctly, and its two independent signals — revision-1 birth scheme and relative PV vintage — agree on every MIXED tenant.

Related: #3282 (fullnameOverride pin), #3335 (etcd adoption backup gate — separate, also required for the 1.5.x→1.6 path).

Screenshots

Not a UI change.

Downstream repositories

  • No downstream repository is affected by this change

Walked the trigger map against the diff: no package added/renamed under packages/{apps,extra}/, the packages/core/platform/values.yaml change is only the migrations targetVersion bump (no spec.components.platform.values.* key changes), no variant/bundle/component changes, no asset renames, no ApplicationDefinition semantic changes.

Release note

fix(seaweedfs): the 1.6 upgrade no longer renames a SeaweedFS instance away from its data. The naming guard now runs in the chart a platform upgrade actually re-renders and refuses when both pre- and post-4.31 naming generations exist; hack/seaweedfs-naming-audit.sh and docs/operations/seaweedfs-431-rename-recovery.md guide recovery, and the refusal is expected for tenants that passed through 1.5.x. Cluster-scoped COSI RBAC is named per namespace again (the 4.31 release-based names collided across tenants), and the seaweedfs-db hand-over runs for every instance name — previously an instance not named `seaweedfs` had its filer metadata database pruned on upgrade; new migration 53 repairs clusters that already ran the old hand-over.

Summary by CodeRabbit

  • New Features
    • Added fail-closed upgrade protections for SeaweedFS naming migrations (mixed/damaged classification), including safer behavior when cluster visibility is limited.
    • Added cluster-scoped RBAC uniqueness safeguards to prevent cross-tenant name collisions during rendering.
    • Improved SeaweedFS database adoption/repair migrations and strengthened post-delete cleanup ownership checks.
  • Bug Fixes
    • Hardened SeaweedFS 4.31 rename recovery and PV rebind flow, including reclaim policy preservation and long/non-default instance-name edge cases.
  • Documentation
    • Expanded the SeaweedFS 4.31 rename-recovery runbook with clarified auditing, verification, and escalation.
  • Tests
    • Added/expanded integration and Helm rendering tests for the above scenarios and refusal/fail-closed behavior.

@github-actions github-actions Bot added size/XXL This PR changes 1000+ lines, ignoring generated files area/storage Issues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor) kind/bug Categorizes issue or PR as related to a bug labels Jul 17, 2026
@dosubot dosubot Bot added area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) backport Should change be backported on previous release labels Jul 17, 2026
@coderabbitai

coderabbitai Bot commented Jul 17, 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

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: 8563d074-99aa-4176-b9de-39ffc04e6557

📥 Commits

Reviewing files that changed from the base of the PR and between a35ae9e and bd770b2.

📒 Files selected for processing (31)
  • docs/operations/seaweedfs-431-rename-recovery.md
  • hack/migration-seaweedfs-db-adopt.bats
  • hack/seaweedfs-guard-parity.bats
  • hack/seaweedfs-naming-audit.bats
  • hack/seaweedfs-naming-audit.sh
  • hack/testdata/migration-seaweedfs-db/kubectl
  • packages/core/platform/images/migrations/migrations/43
  • packages/core/platform/images/migrations/migrations/53
  • packages/core/platform/images/migrations/migrations/lib/seaweedfs-db-adopt.sh
  • packages/core/platform/values.yaml
  • packages/extra/seaweedfs/Makefile
  • packages/extra/seaweedfs/templates/_naming.tpl
  • packages/extra/seaweedfs/templates/hooks/cleanup.yaml
  • packages/extra/seaweedfs/templates/seaweedfs.yaml
  • packages/extra/seaweedfs/tests/cleanup_client_test.yaml
  • packages/extra/seaweedfs/tests/cleanup_named_test.yaml
  • packages/extra/seaweedfs/tests/cleanup_test.yaml
  • packages/extra/seaweedfs/tests/fullname_override_named_test.yaml
  • packages/extra/seaweedfs/tests/fullname_override_test.yaml
  • packages/extra/seaweedfs/tests/guard_fail_closed_test.yaml
  • packages/system/seaweedfs/Makefile
  • packages/system/seaweedfs/charts/seaweedfs/templates/cosi/cosi-cluster-role.yaml
  • packages/system/seaweedfs/charts/seaweedfs/templates/shared/cluster-role.yaml
  • packages/system/seaweedfs/patches/cluster-scoped-names-per-namespace.patch
  • packages/system/seaweedfs/templates/_naming.tpl
  • packages/system/seaweedfs/templates/cluster-scoped-rbac-guard.yaml
  • packages/system/seaweedfs/templates/naming-guard.yaml
  • packages/system/seaweedfs/tests/cluster_scoped_names_test.yaml
  • packages/system/seaweedfs/tests/cluster_scoped_rbac_guard_test.yaml
  • packages/system/seaweedfs/tests/naming_guard_test.yaml
  • packages/system/seaweedfs/values.yaml
🚧 Files skipped from review as they are similar to previous changes (26)
  • packages/core/platform/images/migrations/migrations/53
  • packages/extra/seaweedfs/templates/_naming.tpl
  • packages/system/seaweedfs/templates/_naming.tpl
  • packages/extra/seaweedfs/tests/cleanup_client_test.yaml
  • packages/extra/seaweedfs/tests/cleanup_named_test.yaml
  • packages/system/seaweedfs/patches/cluster-scoped-names-per-namespace.patch
  • packages/system/seaweedfs/values.yaml
  • packages/system/seaweedfs/charts/seaweedfs/templates/cosi/cosi-cluster-role.yaml
  • packages/system/seaweedfs/charts/seaweedfs/templates/shared/cluster-role.yaml
  • packages/core/platform/images/migrations/migrations/43
  • packages/core/platform/values.yaml
  • hack/seaweedfs-guard-parity.bats
  • packages/extra/seaweedfs/tests/guard_fail_closed_test.yaml
  • packages/system/seaweedfs/tests/cluster_scoped_rbac_guard_test.yaml
  • packages/extra/seaweedfs/templates/hooks/cleanup.yaml
  • packages/extra/seaweedfs/tests/cleanup_test.yaml
  • packages/extra/seaweedfs/tests/fullname_override_test.yaml
  • packages/extra/seaweedfs/templates/seaweedfs.yaml
  • hack/testdata/migration-seaweedfs-db/kubectl
  • hack/seaweedfs-naming-audit.bats
  • packages/core/platform/images/migrations/migrations/lib/seaweedfs-db-adopt.sh
  • packages/system/seaweedfs/tests/cluster_scoped_names_test.yaml
  • packages/extra/seaweedfs/tests/fullname_override_named_test.yaml
  • packages/system/seaweedfs/tests/naming_guard_test.yaml
  • hack/migration-seaweedfs-db-adopt.bats
  • hack/seaweedfs-naming-audit.sh

📝 Walkthrough

Walkthrough

SeaweedFS 4.31 handling now includes fail-closed naming guards, read-only audit classification, safer PV/PVC recovery, shared database adoption migrations, ownership-checked cleanup, per-tenant RBAC names, and expanded tests and runbook guidance.

Changes

SeaweedFS 4.31 safety and recovery

Layer / File(s) Summary
Naming guards and audit classification
hack/seaweedfs-naming-audit.*, hack/seaweedfs-guard-parity.bats, packages/*/seaweedfs/templates/*naming*, packages/*/seaweedfs/tests/*guard*
Naming generations are detected from PVC and StatefulSet evidence, renamed prefixes are reconstructed consistently, ambiguous upgrades fail closed, and audit/parity tests cover truncation, mixed vintages, long names, and MultiZone cases.
Database adoption migration
packages/core/platform/images/migrations/*, hack/migration-seaweedfs-db-adopt.bats, hack/testdata/migration-seaweedfs-db/kubectl
Database ownership handover is centralized, rerun by migration 53→54, protected with keep, and tested for idempotent, invalid-ownership, and fail-closed behavior.
Cleanup and cluster-scoped RBAC
packages/extra/seaweedfs/templates/hooks/cleanup.yaml, packages/system/seaweedfs/charts/..., packages/system/seaweedfs/templates/cluster-scoped-rbac-guard.yaml
Cleanup reclaims seaweedfs-db only for matching owners, Client topologies remain excluded, and cluster-scoped RBAC names use per-tenant service-account values with collision guards.
Recovery runbook
docs/operations/seaweedfs-431-rename-recovery.md
Recovery steps cover audit classification, PV reclaim-policy preservation, damaged and mixed tenants, RBAC cleanup, upgrade sequencing, and final policy restoration.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: area/testing, area/extra

Suggested reviewers: kvaps

Sequence Diagram(s)

sequenceDiagram
  participant Helm
  participant Kubernetes
  participant Audit
  participant Operator
  Helm->>Kubernetes: lookup namespace, PVC, and StatefulSet state
  Kubernetes-->>Helm: naming-generation evidence
  Audit->>Kubernetes: inspect releases, PVCs, and PV timestamps
  Kubernetes-->>Audit: fleet classification and recovery direction
  Operator->>Kubernetes: rebind or remove classified resources
  Helm->>Kubernetes: render upgrade after recovery
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing SeaweedFS upgrade fallout from the 4.31 rename on the 1.5.x→1.6 path.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ 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 fix/seaweedfs-rename-fallout

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Trivy (0.72.0)

Trivy execution failed: 2026-07-20T10:43:37Z FATAL Fatal error run error: fs scan error: scan error: scan failed: failed analysis: post analysis error: post analysis error: cloudformation scan error: fs filter error: fs filter error: walk error range error: stat smartylint.json: no such file or directory: range error: stat smartylint.json: no such file or directory


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.

@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 addresses critical data-integrity risks introduced by the SeaweedFS 4.31.0 chart upgrade in Cozystack v1.5.0. It implements a robust naming guard to prevent the accidental renaming of workloads away from existing data, ensures correct database ownership hand-over for non-default instance names, and restores per-tenant uniqueness for cluster-scoped RBAC objects. These changes ensure that upgrades from v1.4.x to v1.6 are safe and provide clear recovery paths for tenants affected by the v1.5.x upgrade path.

Highlights

  • SeaweedFS Upgrade Safety: Introduced a naming guard in the SeaweedFS chart that prevents accidental data loss during platform upgrades by refusing to render if both pre- and post-4.31 naming generations are detected.
  • Database Adoption Fix: Updated migration 43 and added migration 53 to correctly hand over the seaweedfs-db CNPG Cluster for all instance names, preventing metadata pruning on upgrade.
  • RBAC Uniqueness: Restored per-namespace naming for cluster-scoped RBAC objects to prevent cross-tenant collisions caused by the 4.31 SeaweedFS release.
  • Recovery Documentation: Added hack/seaweedfs-naming-audit.sh and a comprehensive recovery runbook to guide operators through resolving duplicate naming generations.
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: **/*.patch (1)
    • packages/system/seaweedfs/patches/cluster-scoped-names-per-namespace.patch
  • Ignored by pattern: **/charts/** (2)
    • packages/system/seaweedfs/charts/seaweedfs/templates/cosi/cosi-cluster-role.yaml
    • packages/system/seaweedfs/charts/seaweedfs/templates/shared/cluster-role.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 addresses critical data-loss and upgrade issues related to the SeaweedFS 4.31 rename regression and the 1.5.0 database split. It introduces robust naming-migration guards in both system/seaweedfs and extra/seaweedfs to prevent workloads from being renamed away from live data or adopting incorrect duplicate sets. It also refactors the database hand-over logic into a shared, fail-closed migration helper, adds migration 53 to repair previously skipped non-default instances, and implements a comprehensive fleet audit script. Additionally, it ensures cluster-scoped RBAC uniqueness per tenant and introduces extensive unit testing suites to prevent future regressions. Feedback is provided regarding a potentially non-existent busybox:1.37 image tag in the recovery runbook, which should be updated to a stable version like 1.36 to avoid ImagePullBackOff errors.

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.

restartPolicy: Never
containers:
- name: inspect
image: busybox:1.37

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 busybox:1.37 image tag appears to be a typo as it does not exist on Docker Hub. This will cause an ImagePullBackOff error when an operator follows this runbook. Please use a valid and available tag, for example 1.36, which is the latest stable version.

Suggested change
image: busybox:1.37
image: busybox:1.36

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.

Thanks, but busybox:1.37 does exist on Docker Hub — it's the 1.37.0 tag (last pushed 2026-05-22), which is newer than 1.36. busybox:1.37 pulls fine, so there's no ImagePullBackOff to avoid here and pinning back to 1.36 would move the runbook backwards. Leaving it as is.

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

🤖 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 `@docs/operations/seaweedfs-431-rename-recovery.md`:
- Around line 146-159: The PV recovery sequence in
docs/operations/seaweedfs-431-rename-recovery.md:146-159 must fail closed: make
recording the original reclaim policy and patching the PV to Retain explicit
success prerequisites before deleting the PVC, rather than relying on ambient
shell settings; preserve the existing ordering and abort on either failure. At
docs/operations/seaweedfs-431-rename-recovery.md:381-382, remove the
original-reclaim-policy annotation only after restoring the original reclaim
policy succeeds.
- Around line 381-382: Update the recovery commands for the persistent volume so
the annotation removal runs only after the reclaim-policy patch succeeds. Chain
the kubectl operations using failure-aware command sequencing, preserving the
cozystack.io/original-reclaim-policy annotation when restoration fails.

In `@packages/extra/seaweedfs/templates/hooks/cleanup.yaml`:
- Around line 185-188: Update the owner lookup in the cleanup hook so kubectl
failures such as Forbidden, timeout, or API errors are propagated and fail the
Job instead of being converted to an empty owner. Remove the unconditional
failure suppression around the kubectl call, while preserving the existing
empty-owner branch only when the seaweedfs-db Cluster is genuinely absent.

In `@packages/extra/seaweedfs/tests/fullname_override_test.yaml`:
- Around line 7-14: The two-generation state documentation must consistently
describe presence-only refusal, without claiming D-wedged tenants are adopted.
Update the summary in packages/extra/seaweedfs/tests/fullname_override_test.yaml
lines 7-14 to state that D-wedged tenants are refused because readiness cannot
prove the duplicate never served, and align the corresponding state summary in
packages/extra/seaweedfs/templates/seaweedfs.yaml lines 137-150; no behavioral
classifier change is required.
🪄 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: b8acf36b-a061-4411-aa54-e330d9dc0c19

📥 Commits

Reviewing files that changed from the base of the PR and between df541b9 and beab659.

📒 Files selected for processing (31)
  • docs/operations/seaweedfs-431-rename-recovery.md
  • hack/migration-seaweedfs-db-adopt.bats
  • hack/seaweedfs-guard-parity.bats
  • hack/seaweedfs-naming-audit.bats
  • hack/seaweedfs-naming-audit.sh
  • hack/testdata/migration-seaweedfs-db/kubectl
  • packages/core/platform/images/migrations/migrations/43
  • packages/core/platform/images/migrations/migrations/53
  • packages/core/platform/images/migrations/migrations/lib/seaweedfs-db-adopt.sh
  • packages/core/platform/values.yaml
  • packages/extra/seaweedfs/Makefile
  • packages/extra/seaweedfs/templates/_naming.tpl
  • packages/extra/seaweedfs/templates/hooks/cleanup.yaml
  • packages/extra/seaweedfs/templates/seaweedfs.yaml
  • packages/extra/seaweedfs/tests/cleanup_client_test.yaml
  • packages/extra/seaweedfs/tests/cleanup_named_test.yaml
  • packages/extra/seaweedfs/tests/cleanup_test.yaml
  • packages/extra/seaweedfs/tests/fullname_override_named_test.yaml
  • packages/extra/seaweedfs/tests/fullname_override_test.yaml
  • packages/extra/seaweedfs/tests/guard_fail_closed_test.yaml
  • packages/system/seaweedfs/Makefile
  • packages/system/seaweedfs/charts/seaweedfs/templates/cosi/cosi-cluster-role.yaml
  • packages/system/seaweedfs/charts/seaweedfs/templates/shared/cluster-role.yaml
  • packages/system/seaweedfs/patches/cluster-scoped-names-per-namespace.patch
  • packages/system/seaweedfs/templates/_naming.tpl
  • packages/system/seaweedfs/templates/cluster-scoped-rbac-guard.yaml
  • packages/system/seaweedfs/templates/naming-guard.yaml
  • packages/system/seaweedfs/tests/cluster_scoped_names_test.yaml
  • packages/system/seaweedfs/tests/cluster_scoped_rbac_guard_test.yaml
  • packages/system/seaweedfs/tests/naming_guard_test.yaml
  • packages/system/seaweedfs/values.yaml

Comment thread docs/operations/seaweedfs-431-rename-recovery.md
Comment thread docs/operations/seaweedfs-431-rename-recovery.md Outdated
Comment thread packages/extra/seaweedfs/templates/hooks/cleanup.yaml Outdated
Comment thread packages/extra/seaweedfs/tests/fullname_override_test.yaml Outdated
@IvanHunters

IvanHunters commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Updated — this replaces my earlier verdict on this comment. The first pass reasoned about the migration shell portability but did not run the shipped test under the CI shell; re-running with that check flips the verdict.

Verdict

NOT LGTM

One MAJOR regression, confirmed by this PR's own red Unit & controller tests job. Everything else verified clean (upgrade path, config-toggle corners, per-namespace RBAC, cleanup hook, guard parity, vendored-patch wiring).

Findings

[MAJOR] packages/core/platform/images/migrations/migrations/43:23 and …/53:41 — migration bashism under #!/bin/sh breaks its own test under dash

Both migrations start with #!/bin/sh + set -euo pipefail, and the new test hack/migration-seaweedfs-db-adopt.bats runs them as sh "$MIG_DIR/43" / sh "$MIG_DIR/53". On the GitHub ubuntu runner /bin/sh is dash, which does not implement pipefail: the script aborts on line 1 with set: Illegal option -o pipefail (exit 2) before doing any work. The test expects rc=0 and gets rc=2, so the Unit & controller tests job is RED (migrations/43: 23: set: Illegal option -o pipefail).

Production is unaffected — the migrations image is FROM alpine, so /bin/sh is busybox ash, which supports pipefail. That is exactly why checking only the runtime image misses this; the failing environment is the test's dash, not production's ash.

Fix (any one):

  • give both migrations #!/bin/bash and exec them via their shebang in the test ("$MIG_DIR/43", not sh "$MIG_DIR/43") — this is what migration 50 already does; or
  • make the scripts dash-clean (drop pipefail); or
  • run them in the test under the production interpreter (bash / busybox ash), never the runner's sh.

Repro without CI: dash packages/core/platform/images/migrations/migrations/43.

Caveats (verified, non-blocking)

  • Upgrade path: migration 53 is strictly above the current highest (52), targetVersion bumped 5354 (no silent skip, not rebase-stale); lib/seaweedfs-db-adopt.sh is idempotent, fails closed on any non-absent kubectl error, and matches the -system suffix for every instance name. Correct — modulo the dash bug above, which blocks the tests that would prove it.
  • Config-toggle matrix (rendered): Simple / MultiZone emit <name>-db + <name>-system with dependsOn: seaweedfs-db satisfied; Client emits zero HelmReleases (both are inside the same {{- if not (eq .Values.topology "Client") }} gate at seaweedfs.yaml:95 and seaweedfs-db.yaml:1), so there is no dangling dependsOn.
  • Cluster-scoped RBAC renders per-namespace-unique (<ns>-seaweedfs-*) via the seaweedfs.compat shim; regression test + runtime guard present. The charts/ edits are the correct shape: patches/cluster-scoped-names-per-namespace.patch exists and the Makefile update target applies it (no silent revert on make update).
  • Cleanup Job: activeDeadlineSeconds + --wait=false + ownership check + release-scoped selector, PSS-restricted. guard-parity.bats pins the two detection blocks byte-identical.
  • NIT: packages/system/seaweedfs/values.yaml:7 default serviceAccountName: "tenant-foo-seaweedfs" reads like a leaked concrete tenant name (harmless — overridden per namespace, and the guard fails closed if it were ever effective).

@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

IvanHunters Fixed in a38843c — thanks, the repro was exact.

Rather than move the shebang or drop pipefail, I took your third option and made the test run the migrations the way run-migrations.sh does: by path, inside the image's own pinned alpine base, read out of the migrations Dockerfile so the interpreter under test can't drift from the one they ship on. pipefail turned out to be load-bearing rather than decorative — lib/cozystack-version.sh pipes the rendered manifest into kubectl apply, so without it a failed render stamps the version from empty input — which is what ruled out the dash-clean variant. Note this makes the unit-test job depend on docker; there's deliberately no host-shell fallback, since every fallback available is a shell production never uses.

One thing the switch surfaced: cozytest.sh's awk generator rewrites every bare } in column 0 into return 0 + }, so a helper that falls off its own end returns 0 regardless of what it ran. The first version of run_migration did exactly that — the three fail-closed tests went green while the container was exiting 1. It now captures and returns the status by hand.

15/15 pass. Verified the interpreter under test is BusyBox v1.37.0 ash with pipefail supported, and that a fail-open mutation in lib/seaweedfs-db-adopt.sh turns the suite red.

On the NIT — packages/system/seaweedfs/values.yaml:7 predates this PR and isn't in its diff, so I've left it out rather than widen the change. Happy to take it separately if you'd like.

@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/seaweedfs-naming-audit.sh (1)

48-56: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Scope set -u to direct execution.

Because this file is sourced by the test harness, the top-level set -u permanently changes the caller’s shell options. A later unset expansion in hack/seaweedfs-naming-audit.bats or cozytest.sh can therefore abort the harness unexpectedly. Move nounset setup into the non-library execution path, or explicitly restore the caller’s options.

🤖 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/seaweedfs-naming-audit.sh` around lines 48 - 56, Scope the set -u
nounset option to the script’s direct-execution path rather than applying it at
top level. Preserve the existing POSIX sh behavior when
hack/seaweedfs-naming-audit.sh runs directly, while ensuring sourcing it from
hack/seaweedfs-naming-audit.bats or cozytest.sh does not alter the caller’s
shell options.
🤖 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/seaweedfs-naming-audit.sh`:
- Around line 48-56: Scope the set -u nounset option to the script’s
direct-execution path rather than applying it at top level. Preserve the
existing POSIX sh behavior when hack/seaweedfs-naming-audit.sh runs directly,
while ensuring sourcing it from hack/seaweedfs-naming-audit.bats or cozytest.sh
does not alter the caller’s shell options.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a0ced512-aa06-44b6-8dbb-40e613051598

📥 Commits

Reviewing files that changed from the base of the PR and between a38843c and a35ae9e.

📒 Files selected for processing (1)
  • hack/seaweedfs-naming-audit.sh

…actually renames

The class-S guard from #3282 lives in extra/seaweedfs, but that render is not
in the path of a platform upgrade: the <name>-system HelmRelease pulls
system/seaweedfs from a platform-managed ExternalArtifact, so bumping the
platform upgrades the release directly from the new artifact and applies
fullnameOverride: seaweedfs without extra/seaweedfs ever re-rendering. On a
tenant installed fresh on 1.5.x that stood up an empty chart-named set beside
the live release-named data, deleted the renamed s3 Service, and left S3
readable only because the empty set's pods cannot start.

Worse, the damage flips the guard's own classification: creating the
chart-named artifacts makes $legacyData true, so the guard then reports
D-split and sends the operator to runbook Step 3 — which quiesces the renamed
set, i.e. the one holding ALL the data.

- Add the ENFORCING guard to system/seaweedfs (templates/naming-guard.yaml):
  the only render guaranteed to sit between a platform upgrade and the
  tenant's workloads. It also refuses to render if the fullnameOverride pin
  is ever lifted.
- Tell S from D by PVC AGE when both generations exist: PVCs are never
  recreated in place (StatefulSets are, by the adoption hook), so the older
  volume-PVC generation is where the data was born. Renamed-older ⇒ class S
  (damaged variant, new Step 2a), never D-split.
- Fail closed: the release namespace is a lookup canary; an upgrade that
  cannot see the cluster refuses instead of classifying blind. Client-side
  installs (CI lint/unittest) still render — they never touch live data.
- Mirror the same classification in extra/seaweedfs so the operator-facing
  SeaweedFS application reports the correct class.
- Tests now model the ExternalArtifact upgrade path in the chart it actually
  runs through (the old ones mocked lookups only in extra/seaweedfs, which is
  why this shipped green), including the damaged-S tenant observed live.
- Runbook: add S-damaged audit classification (PVC age), Step 2a cleanup of
  the empty chart-named set, and the D-wedged anti-affinity stall workaround
  observed on 3-node clusters.

helm-unittest 1.0.3 ignores release.isUpgrade, so the canary refusal is
verified with 'helm template --is-upgrade' instead of a unit test.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
… pinned fullname

ClusterRoles and ClusterRoleBindings are cluster-scoped: one object per name,
cluster-wide. This chart installs once per tenant, so every cluster-scoped name
it renders must be unique per install. Four of them were not.

Upstream 4.31 switched them from global.serviceAccountName to seaweedfs.fullname.
That is fatal in combination with the #3282 pin: fullnameOverride: seaweedfs is
what adopts the running NAMESPACED workloads across the rename, so fullname is by
construction the SAME string in every tenant. Every SeaweedFS instance in the
fleet therefore renders seaweedfs-objectstorage-provisioner, seaweedfs-rw-cr and
their bindings — one object, five claimants.

NOT a #3282 regression. Rendering v1.4.5, v1.5.3 and main for the same tenants:

  v1.4.5  tenant-root-seaweedfs-*      tenant-named-seaweedfs-*     unique
  v1.5.3  seaweedfs-system-*           foo-system-seaweedfs-*       COLLIDES
          (identical for every default-named instance; a non-default
           instance name was the only thing that escaped)
  main    seaweedfs-*                  seaweedfs-*                  COLLIDES
          (nothing escapes: the pin removes the last distinguishing input)

So the collision shipped in 1.5.0 with the 4.31 bump; #3282 widens it from
"every default-named instance" to "every instance". Observed live on the upgrade
stand: one ClusterRole/seaweedfs-objectstorage-provisioner annotated to a single
owning tenant, four other tenants' COSI provisioners left with no RBAC, and
tenant-dsplit's helm history recording
  Upgrade failed: original object ClusterRole "seaweedfs-objectstorage-provisioner" not found

The binding is the sharp end: its subject names a ServiceAccount in the release
namespace, so the single live ClusterRoleBinding grants cluster-wide secrets RBAC
to whichever tenant reconciled last, and silently revokes it from the rest.

Fix: put the four names back on global.seaweedfs.serviceAccountName, which
extra/seaweedfs already sets to <namespace>-seaweedfs and which
cosi-provisioner-sa-name.patch already uses for the ServiceAccount and the
binding's subject — the cluster-scoped names were simply left behind. Three of
the four come out byte-identical to pre-4.31, so a 1.4.x tenant adopts them in
place; the fourth (rw-crb) replaces upstream's username-shaped
system:serviceaccount:<sa>:default and is pruned from the release's own manifest.
The namespaced pin is untouched: workloads still render seaweedfs-master/-filer/
-volume, so #3282's adoption is intact.

Uniqueness now rests on a value, so assert it rather than trust it:
templates/cluster-scoped-rbac-guard.yaml refuses to render when the name would
not carry the release namespace. It sits behind the same namespace canary as the
naming guard, because system/seaweedfs's own values.yaml ships a placeholder that
only extra/seaweedfs overrides — a client-side render must not trip on it.

Tests fail on unfixed code: cluster_scoped_names_test asserts two tenants get two
names, and unpatched both render seaweedfs-objectstorage-provisioner (5 failed,
0 passed). They test the vendored chart templates directly, which is where the
rename happens.

The guard includes seaweedfs.compat before reading. extra/seaweedfs sets the OLD
flat key global.serviceAccountName, and the shim is what folds it into the
canonical global.seaweedfs.serviceAccountName; without it the guard compares
against this chart's placeholder default and refuses every real upgrade. Caught
by helm upgrade --dry-run=server against the stand, not by unit tests — the
mocks never carry the flat key.

Verified live with --dry-run=server on the stand: tenant-l (installed pre-4.31,
never upgraded) already owns tenant-l-seaweedfs-objectstorage-provisioner and
tenant-l-seaweedfs-rw-cr, and the fixed chart renders exactly those — adopted in
place, no ownership error. tenant-root renders tenant-root-seaweedfs-* and also
dry-runs clean. Leftovers on the release-based names that a 1.5.x tenant passed
through are inert once every tenant is upgraded (no release renders them any
more) but are not always pruned, since more than one release claimed them; the
runbook adds the sweep.

Review round 2:
- The uniqueness guard used `contains .Release.Namespace $sa`, a substring test
  that accepts namespace `tenant-a` for service account `tenant-ab-seaweedfs` —
  a different namespace's. Anchored to hasPrefix "<namespace>-". It still asserts
  the VALUE the names are built from, not the rendered names, so it cannot catch
  the chart being reverted to name them after the pinned fullname;
  cluster_scoped_names_test.yaml covers that axis and is what actually bites.
- Added the case that would have caught the compat bug: every test set the
  CANONICAL global.seaweedfs.serviceAccountName, while production sets the OLD
  FLAT global.serviceAccountName that only seaweedfs.compat folds in. Deleting
  the include left the suite passing 5/5 while every real upgrade refused. The
  new case sets the flat key; with the include removed it now fails with the
  production error.
- Runbook sweep missed the fourth pre-4.31 name: 4.31 renamed the master-rw
  binding from upstream's username-shaped system:serviceaccount:<sa>:default to
  <sa>-rw-crb, and the old one carries neither suffix nor a tenant prefix, so the
  grep could not see it (live on the stand as
  system:serviceaccount:tenant-l-seaweedfs:default). Also documents the rolling-
  upgrade window: Helm prunes by name without checking ownership, so the first
  tenant onto the fixed chart deletes shared objects a not-yet-upgraded tenant is
  still bound through — COSI provisioning 403s until they reconcile, S3 traffic
  unaffected.

Review round 3:
- hasPrefix "<namespace>-" was still too loose. It accepts `tenant-a-b-seaweedfs`
  for namespace `tenant-a` — which is ALSO the natural value for namespace
  `tenant-a-b`, so two namespaces would accept one string and collide on the very
  object this guard exists to keep unique. extra/seaweedfs sets exactly
  `<namespace>-seaweedfs`, so assert equality with it and nothing looser.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…st the default

Migration 43 performs the v1.5.0 db-split hand-over: it re-owns Cluster/seaweedfs-db
from <name>-system to the new <name>-db release and stamps
helm.sh/resource-policy: keep, so the <name>-system upgrade — whose post-split
chart no longer renders the Cluster — does not prune it as a removed resource.

It compared the owning release name against the literal "seaweedfs-system".
SeaweedFS is a user-creatable kind, so an instance named `foo` is owned by
`foo-system` and was silently skipped: no re-own, no keep. The <name>-system
upgrade then deleted the Cluster, CNPG took its PVC with it, and the filer
metadata — the index for every object in that tenant's S3 — was destroyed. Data
loss, not an outage: the volume PVCs still hold the bytes, nothing can find them.

Verified live on the upgrade stand, which is a natural control group. All five
tenants sit in the SAME failed-upgrade loop (<name>-system last-deployed is
rev 1, a pre-split revision whose manifest still contains the Cluster, so every
retry recomputes the same deletion):

  tenant-root/dsplit/l   instance `seaweedfs`  owner=seaweedfs-db  keep   Cluster alive
  tenant-named           instance `foo`        no Cluster at all          Cluster GONE

The only difference is that migration 43 fired for the default-named instances
and skipped `foo`. That also answers whether `keep` is sufficient: three tenants
survive the identical prune loop with it.

Correction to the reported mechanism: this is the 1.4->1.5 db split (PR #2601,
v1.5.0), not a 1.5->1.6 change — v1.5.3 and main are identical on every
db-split file. And the split does protect the object; it just only protected the
instances someone happened to name `seaweedfs`.

- lib/seaweedfs-db-adopt.sh: shared hand-over matching the `-system` SUFFIX, so
  `foo-system` -> `foo-db` exactly as `seaweedfs-system` -> `seaweedfs-db`.
  Idempotent, and refuses a release named literally `-system` rather than
  annotating an owner of `-db` that no release would claim.
- Migration 43 sources it: covers clusters upgrading from before 43 (1.4.x).
- Migration 53 (new) re-runs it: migrations never re-run, so any cluster already
  at >= 44 ran the hardcoded version and is still exposed. As a pre-upgrade hook
  it lands before <name>-system re-renders, closing the window on THIS upgrade.
  This is the one that matters for 1.5.x -> 1.6.
- targetVersion 53 -> 54.

Tests fail on unfixed code: the non-default-name case produces no ANNOTATE at
all against the hardcoded 43, while the default-name case passes — which is
precisely the bug's shape. They drive the real migration scripts against a fake
kubectl, following hack/migration-50-etcd-adopt.bats.

LIMIT, stated plainly: a Cluster that is already deleted cannot be recovered by
either migration — there is nothing left to re-annotate and the PVC went with
it. Such a tenant needs seaweedfs-db restored from a backup. Runbook Step 0 adds
the ownership audit that distinguishes at-risk from already-lost, and records
that <name>-db can report Ready while its Cluster is gone (it rendered fine; a
later <name>-system prune removed it and Flux has not re-checked) — so the
audit trusts `kubectl get cluster`, not the HelmRelease status.

Review round 2 — two ways this still lost the database:

- OWNERSHIP IS NOT SAFETY. The helper skipped any Cluster already owned by
  <name>-db, treating ownership as proof the hand-over was done. It is not: where
  the hand-over was skipped, <name>-system prunes the Cluster and <name>-db
  RECREATES it under its own ownership with NO keep, while <name>-system's prune
  baseline still lists it — so the next reconcile deletes it again. That is the
  observed delete/recreate loop, and the skip walked straight past it. Verified on
  the stand: tenant-l and tenant-root are seaweedfs-db-owned AND their
  <name>-system deployed revision (rev 1) still contains the Cluster, so only keep
  saves them; tenant-fresh is seaweedfs-db-owned without keep but was installed
  after the split, so its baseline never had one. Telling those apart needs the
  release's deployed manifest, which this script cannot read cheaply or reliably.
  The costs are asymmetric — a needless keep leaves an orphan on delete (now
  reclaimed by the cleanup hook), a missing one loses the database — so keep is
  stamped on every Cluster owned by either side of the split.

- FAIL OPEN, THEN STAMP. `set -euo pipefail` does not abort on a failing command
  substitution in a `for` word-list: the loop ran zero times, the script continued,
  and stamp_cozystack_version ran anyway. Migrations never re-run, so one throttle,
  RBAC hiccup, or not-yet-established CNPG apiservice behind the pre-upgrade hook
  permanently left every at-risk tenant exposed with no later migration to catch
  them. Reproduced: against the old helper a failing fleet scan still logged
  STAMP 54. Now every kubectl failure is fatal EXCEPT the two that genuinely mean
  "nothing to do" — the CNPG resource type not being served (the fail-open that IS
  load-bearing: a cluster without CNPG must still upgrade) and a Cluster vanishing
  between scan and read. An unreadable annotation is no longer indistinguishable
  from an absent one, and the unowned case now warns instead of passing silently.

- ORPHAN ON DELETE. `keep` is permanent (removal deferred to the 1.7 batch
  migrations) and also survives the <name>-db release's own uninstall, so
  `kubectl delete seaweedfs <name>` left the CNPG Cluster, its Postgres pods and
  its PVCs behind — the cleanup hook selects
  app.kubernetes.io/instance=<name>-system, which the db PVCs
  (cnpg.io/cluster=seaweedfs-db) do not carry. Migration 53 widened that from
  pre-1.5 default-named tenants to every instance name, so the hook now deletes
  the Cluster explicitly; its PVCs carry ownerReferences to it and follow.

- Tests assert the stamped VERSION (44 / 54), not a bare "STAMP": a wrong number
  would loop run-migrations.sh forever and the old assertion could not tell.

Review round 3 — the reclaim introduced a data-loss path of its own:

- DELETING ONE APP DELETED ANOTHER APP'S DATABASE. The db chart hardcodes the
  Cluster name `seaweedfs-db` for every instance, and the reclaim was scoped by
  that name in the namespace rather than by what the release owns.
  templates/seaweedfs-db.yaml gates the <name>-db HelmRelease on
  `topology != "Client"`; the cleanup hook had no such gate. Verified by render:
  `helm template s3-remote . --set topology=Client` emits ZERO s3-remote-db
  HelmReleases yet still emitted the Role granting delete on clusters/seaweedfs-db
  AND `kubectl delete cluster.postgresql.cnpg.io -n tenant-root seaweedfs-db`. So
  in a namespace running a server instance plus a Client, deleting the CLIENT
  destroyed the SERVER's filer metadata — CNPG takes the PVC via ownerReferences —
  for an app the operator never touched. Reachable the same way via a second Simple
  instance, whose <name>-db loses Helm's ownership check but which still installs
  and still carries the hook. main's hook was instance-scoped; this was new, and
  mine.
  Now gated exactly as seaweedfs-db.yaml is, and the Job re-checks
  meta.helm.sh/release-name == <release>-db at run time before deleting. The gate
  stops the permission being granted where it can never be legitimate; the runtime
  check stops the delete where it can.

- `|| echo` turned any API/authz/admission failure into exit zero, so backoffLimit
  never retried and hook-delete-policy removed the only cleanup Job while the
  keep-protected Cluster stayed orphaned. A failed reclaim now fails the Job.

- TEST SHELL. The migrations run under /bin/sh = busybox ash (the image is FROM
  alpine, run-migrations.sh is #!/bin/sh), but the tests drove them with bash — so
  the fail-closed guarantees were asserted in a shell that never runs them, which
  matters more than usual given the bug they cover was a fail-open. The scripts are
  now invoked via `sh` and the fake kubectl is POSIX sh. Verified directly against
  the production base image (busybox 1.37.0 / alpine:3.24): `set -o pipefail` is
  supported, every script passes `ash -n`, and the failing-fleet-scan path exits
  non-zero without stamping under real ash. The recipe is in the bats header.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…n holds the data

The guard told a class-S tenant from a D tenant by comparing PVC creationTimestamps,
on the premise that "PVCs are never recreated in place, so the older generation is
where the data was born". The premise is false, and this runbook is the counter-
example: Step 2's re-bind DELETES each release-named claim and recreates it under
the chart name against the same PV.

So a tenant interrupted part-way through Step 2 — after volume-0, before volume-1 —
has data1-seaweedfs-volume-0 created SECONDS ago holding real data, beside
data1-seaweedfs-system-volume-1 with the original timestamp also holding real data.
That is the exact inversion of the rule. Reproduced against the old template:

  keeps its data on the release-named volume PVCs (created 2026-06-20T07:01:59Z,
  OLDER than the chart-named data1-seaweedfs-volume-* claims created
  2026-07-17T09:30:00Z) ... Remove the empty chart-named set ... (Step 2a, then Step 2)

Step 2a then runs `grep -E '^data1-seaweedfs-volume' | xargs kubectl delete pvc` —
deleting the claim Step 2 had just re-bound — and Step 2 had already restored the
PV's original reclaim policy, so on any Delete-policy StorageClass the volume and
the bytes go with it. The guard's own recovery path was a data-loss instruction.

Two more holes in the same classifier:

- The S-damaged branch was gated on $systemPVC, which requires the PVC name to
  contain "seaweedfs". seaweedfs.componentName truncates the fullname to 56 chars
  before appending -volume, so an instance name >= ~40 chars drops the chart name
  off the PVC name — the very case fullname_override_named_test.yaml exists for.
  Probed with `archive-of-quarterly-financial-statements-x1`: damaged + renamed set
  Ready fell through to the D-split branch ("is running two live SeaweedFS sets ...
  Stop the split"), sending the operator to quiesce the set holding ALL the data —
  the exact misclassification the parent commit exists to remove. Damaged + not
  Ready fired no branch at all and rendered through unguarded.
- readyReplicas == 0 was read as proof the duplicate never served. It is a
  snapshot: a duplicate that crashed, was scaled down, or lost readiness AFTER
  serving writes reads identically, and adoption strands whatever it wrote.

There is no sound signal here. Claim timestamps are mutable, StatefulSets are
recreated by the adoption hook, and liveness is a snapshot — nothing durable in the
object graph distinguishes "empty set a bad upgrade created" from "the data,
mid-re-bind". Rather than add a fourth heuristic, the guard now decides only what
is decidable:

  legacy generation only (or nothing) -> render; adoption is correct
  release-named generation only       -> refuse, class S (Step 2)
  BOTH generations                    -> refuse; an operator classifies

Both generation flags now OR the PVC and StatefulSet evidence, so the truncated
long-name tenant is seen via its label-matched StatefulSet.

The cost is bounded and lands only where it should. A duplicate exists only on a
tenant that passed through the broken 4.31 rename in 1.5.x — already damaged,
already needing an operator. A 1.4.x tenant upgrading straight to 1.6 never
renames, so it has one generation and is untouched. Confirmed on the stand:
tenant-l (1.4.5-era, never upgraded) is legacy-only and dry-runs clean; the four
both-generation tenants all already need an operator. What is lost is D-wedged
auto-adoption, which was resting on the readyReplicas assumption above.

Classification moves to the runbook, where a human has signals a template does not:
Step 1 now reads the ages of the PVs (durable — they survive the re-bind) via
.spec.volumeName instead of the claims, reports MIXED instead of picking, and walks
the operator through interrupted-Step-2 first, then strict age separation, then
verifying emptiness directly, then escalating rather than guessing. Step 2 stashes
the PV's original reclaim policy ON THE PV and leaves it Retain until Step 5
verifies the tenant, so a mis-step during recovery is survivable. Step 2a grows a
precondition that refuses unless every chart-named claim is bound to a strictly
newer PV.

Tests: 7 of 14 in the enforcing suite fail against the old template, including the
interrupted-Step-2 tenant and both truncated-long-name cases. hack/seaweedfs-guard-
parity.bats pins the two copies (system/ enforcing, extra/ sibling) against silent
drift — they cannot share a library, neither chart depends on cozy-lib, and "keep
these in sync" as a comment is the failure class this whole branch is about; it
also pins that creationTimestamp and readyReplicas never come back.

`make test` in both charts now runs helm template --is-upgrade and greps for the
refusal. The fail-closed canary — the headline safety property — had ZERO automated
coverage: guard_fail_closed_test.yaml passes with AND without the fix, because
helm-unittest 1.0.3 ignores release.isUpgrade. Verified the new target fails when
the canary is removed.

Review round 3 — the refusal was right; the justification was false and the
procedure that replaced the chart's judgement was broken on both axes.

THE COMMENT WAS WRONG. It said "nothing durable in the object graph distinguishes
the empty generation from the data mid-re-bind". A durable signal does exist, and
the reviewers were right to call that out: the Helm release history. Verified on
the stand — sh.helm.release.v1.<name>-system.v1 records the scheme the tenant was
BORN under (tenant-root/l/dsplit/named rev 1 render seaweedfs-master => pre-4.31;
tenant-fresh renders seaweedfs-system-master => 1.5.x), and info.first_deployed is
on every retained revision, so anchoring PV creationTimestamps against it survives
history pruning and the Step 2 re-bind (tenant-root: chart-named +10s vs
release-named +2084s; tenant-fresh: +5225s vs +6s).

BUT IT DOES NOT UNLOCK AUTO-ADOPTION, and that is the substantive disagreement
with the round-3 direction. Birth order answers "which generation is ORIGINAL".
Adoption needs "is the OTHER one EMPTY". Those come apart exactly where it
matters: D-wedged (duplicate never scheduled, safe to adopt) and D-split
(duplicate served writes, adopting strands them) are BOTH born pre-4.31 and are
therefore identical on every durable signal. Verified: tenant-root and
tenant-dsplit have the same rev-1 scheme and the same first_deployed deltas, yet
one duplicate served and the other did not. The only thing that ever separated
them was readyReplicas, which is a snapshot and not evidence. So the render keeps
refusing on presence, and the comment now says why — naming the signal, so nobody
re-derives the broken one.

The signal IS used where it belongs, in the audit.

- naming-guard.yaml / seaweedfs.yaml: match the release-named generation by
  RECONSTRUCTING the name 4.31 gives this release's volume component
  (templates/_naming.tpl, replaying seaweedfs.fullname + componentName), tested
  FIRST because the prefixes nest. Fixes a blocker: an instance legitimately named
  `seaweedfs-volume` renders seaweedfs-volume-system-volume and
  data1-seaweedfs-volume-system-volume-0, which BOTH satisfy the chart-named
  prefixes — a prefix-only guard read its live storage as legacy and would render
  onto the empty chart-named claims, stranding the real generation. The parity
  bats passed straight through it, because parity pins sameness, not correctness.
  Reconstruction also removes the name-match blind spot at source: a truncated long
  instance name now matches by PVC, not only by StatefulSet.

- hack/seaweedfs-naming-audit.sh + .bats: the classifier is a tested script now.
  Two revisions of it shipped as an untested snippet inside the runbook and both
  were wrong in ways that routed a live tenant into the step that strands its data:
  a selector `^data1-(.*seaweedfs.*)-volume` that also matched the chart-named
  claims, so the release-named range spanned BOTH generations, every tenant read as
  "overlapping / mid-rebind", and a genuine S-damaged tenant was routed AWAY from
  Step 2a into Step 2 — which deletes its data claim, fails to recreate it, then
  deletes its StatefulSets; and a `grep seaweedfs` name match that reported
  "L — nothing to do" for a long-named tenant the chart refuses. Both are covered.
  The audit reports L/S/MIXED matching the guard, names the original generation
  from both durable signals, detects MID-REBIND (both generations at
  first_deployed), and states plainly that "original" is not "empty" and that
  emptiness must be established from the duplicate's volume files before deleting.
  A bats case renders _naming.tpl through helm and compares it to the script's
  reconstruction, so the two implementations cannot drift.

- Runbook: Step 1 calls the script. Steps 3/4 were circular — Step 3 ended in an
  upgrade the presence-gated guard refuses, and Step 4 told the operator to clear
  duplicates AFTER an upgrade that cannot run until they are gone. Step 3 now
  removes the duplicate BEFORE the upgrade and re-runs the audit to prove it; Step 4
  starts from "every tenant reads L or S". D-wedged no longer claims to need
  nothing. Step 3's "enumerate fids that resolve only on the duplicate" was not an
  executable procedure and is now an explicit escalation: two masters allocate
  volume IDs from independent sequences into one shared seaweedfs-db, so there is
  no supported tool that reconciles two volume-ID spaces against one metadata
  store. Step 2's re-bind restores the cleanup hook's labels on the recreated
  claims, without which a recovered tenant leaks its volumes on a later delete.

Fixture corrected: data1-...-x1-system-volume-0 is not a name the chart renders;
componentName gives ...-x1-system-seaw-volume-0.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…clock window

The audit named the candidate duplicate by measuring each generation's oldest
PV against the release's first_deployed, with an absolute 120-second window
for the mid-rebind case. The shipped StorageClasses are WaitForFirstConsumer,
so PVs are provisioned at pod-SCHEDULE time — on a cold cluster minutes after
first_deployed — and a tenant interrupted mid-way through Step 2's re-bind
(both generations sitting on original-vintage PVs) fell out of the window and
into a branch decided by which volume index happened to provision first. The
advice printed there routes the operator to a step that deletes the claims
Step 2 had not yet re-bound.

Replace the window with the relative rule runbook Step 2a already enforces: a
generation is the candidate duplicate only when EVERY one of its bound PVs is
strictly newer than every bound PV of the other generation. Overlapping or
tying vintages name no candidate — finish Step 2 or escalate. first_deployed
is still printed, as context only.

The direction decision is now a pure function (classify_mixed_direction) so
the bats suite drives it directly: clean duplicate both ways, interrupted
re-bind, tie, and a no-clock case pinning the WaitForFirstConsumer regression.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The tenant module is the supported way to run SeaweedFS and it hardcodes the
instance name: packages/apps/tenant/templates/seaweedfs.yaml creates the
HelmRelease as `seaweedfs`, and a tenant can only enable or disable it. Every
shell selector in this runbook assumes that name, so say so up front, and route
instances created directly against the API under other names to escalation
instead of through loops whose name filters provably cannot see truncated
claims. The ~40-char zone/pool key limit of the guard's reconstruction is
called out as an accepted limit for default-named instances too.

Two content fixes ride along, both for findings the final review pass raised:

- The emptiness check told the operator to kubectl exec the candidate's own
  volume pod — which for a D-wedged duplicate can never start; that is the
  definition of the class, so the check was unexecutable exactly where it
  matters most and every D-wedged tenant funneled to escalation. Mount each
  bound candidate claim read-only in a scratch pod instead (a Pending claim
  has no PV and is empty by construction). The weed shell cross-check stays
  on the authoritative master, which is running.

- Step 1's description of the audit now matches the audit itself: direction
  is decided by the relative strictly-newer PV rule (the same precondition
  Step 2a enforces), and an interrupted re-bind reads as overlapping vintages
  with no candidate — not by a clock window anchored on first_deployed.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…sification

MultiZone (and Simple-with-pools) tenants render no plain `volume` component —
extra/seaweedfs sets volume.enabled=false and the vendored chart emits one
component per zone key with suffix `volume-<key>`, truncated at
(62 - len(suffix)), which is SHORTER than the 56-char cut the guard's
renamedVolumePrefix replays. The final review pass flagged this divergence as a
guard bypass. For the supported instance the prefixes cannot diverge: the
tenant module hardcodes the name `seaweedfs`, the fullname is 16 chars, and
nothing truncates until a zone/pool key of ~40+ characters. Three new cases pin
that supported path — legacy-only zones adopt, release-named-only zones refuse
as class S, both generations refuse — since the suite previously had no zone
shapes at all and the zone objects are the ONLY evidence on a MultiZone tenant.

The divergence beyond that boundary (long instance names or absurd keys) is an
accepted, documented limit, not a fix: decided in the 1.6 triage, recorded in
_naming.tpl (both copies, kept in lockstep) and the runbook's Scope section.
Instance names are fixed by the tenant module, so absurd keys are the only
reachable trigger; revisit if instance naming is ever opened up.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…andling

The 1.5.x->1.6 rename recovery runbook deletes each volume PVC after
switching its PV to Retain, but the switch was never gated on success.
With no `set -e`, a failed `kubectl patch ... Retain` left the PV at
Delete and the following `delete pvc` took the data with the claim.

Gate the delete on both the annotation record and the Retain patch
succeeding, aborting the re-bind loop otherwise, so no PVC is deleted
while its PV is unprotected. In the Step 5 restore loop, chain the
annotation removal after the restore patch with `&&` so a failed patch
keeps the saved policy for a retry.

Address review feedback from coderabbitai on
docs/operations/seaweedfs-431-rename-recovery.md.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The post-delete cleanup hook read Cluster/seaweedfs-db's Helm owner with
`|| true`, so a Forbidden, a timeout or any API error collapsed to an
empty owner, took the "nothing to reclaim" branch, and let the Job
complete -- permanently orphaning the keep-protected Cluster the hook
exists to reclaim, with nothing else that will ever collect it.

Only a genuine NotFound now counts as absence; every other error prints
the failure and exits non-zero so the Job retries. This matches the
fail-closed handling the delete path already had. Add a regression
assertion that the ownership lookup no longer swallows failures.

Address review feedback from coderabbitai on
packages/extra/seaweedfs/templates/hooks/cleanup.yaml.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The naming guard now refuses every two-generation state, but two
comments in extra/seaweedfs still described a D-wedged duplicate (zero
ready replicas) as safe to adopt and rendering through. readyReplicas is
a snapshot, not proof the duplicate never served, so it cannot be told
apart from D-split and the guard refuses it too.

Align the state summary in the seaweedfs template comment and the test
header with the presence-only refusal behaviour. No classifier change --
comments only.

Address review feedback from coderabbitai on
packages/extra/seaweedfs/tests/fullname_override_test.yaml.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Address review feedback from IvanHunters on
packages/core/platform/images/migrations/migrations/43:23:

The suite invoked the migrations as `sh "$MIG_DIR/<n>"`. On the CI runner
/bin/sh is dash, which has no `set -o pipefail`, so both scripts aborted
on their `set` line with "set: Illegal option -o pipefail" (exit 2)
before doing any work, and the job went red. Production was unaffected —
the migrations image is FROM alpine, so /bin/sh there is busybox ash —
which is exactly why checking the runtime image missed it.

Rather than change a shebang or drop pipefail, run the migrations the way
run-migrations.sh does: by path, inside the image's own pinned alpine
base, read out of the migrations Dockerfile so the interpreter under test
cannot drift from the one they ship on. pipefail is load-bearing here —
lib/cozystack-version.sh pipes the rendered manifest into `kubectl
apply`, so without it a failed render stamps the version from empty
input — and a shell that lacks it can prove nothing about that path.

run_migration() returns the container's status explicitly: cozytest.sh's
awk generator rewrites every bare `}` in column 0 into `return 0` + `}`,
so a helper that falls off its own end reports success no matter what it
ran, and every fail-closed assertion would have passed vacuously.

15/15 pass. Verified the interpreter under test is BusyBox v1.37.0 ash
with pipefail supported, and that a fail-open mutation in
lib/seaweedfs-db-adopt.sh turns the suite red.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
hack/seaweedfs-naming-audit.bats sources the audit script at top level,
and cozytest.sh sources the converted test file into its own /bin/sh --
dash 0.5.12 on the Ubuntu CI runner. dash has no `pipefail`, so
`set -uo pipefail` aborted the runner before the first @test and took
the whole file down with it:

    hack/cozytest.sh: 48: set: Illegal option -o pipefail

The bash shebang could not prevent this: `.` executes the file's
contents in the caller's shell, so the shebang is only a comment there.

The script was already POSIX apart from those two lines -- no arrays,
no [[, no local, no bashisms at all -- so the script becomes POSIX sh
rather than the runner becoming bash. That keeps the shell under test
and the shell at run time the same one, instead of testing under dash
while shipping under bash.

Dropping `pipefail` changes no behaviour: the script never sets -e and
never reads a pipeline's exit status, reporting failure as empty output
checked with [ -n ... ].

Verified under dash (ubuntu:24.04): 11/11 tests pass, and the script
runs clean against a live cluster, classifying five seaweedfs tenants
with the revision-1 scheme and the PV ages agreeing on every MIXED
case. shellcheck -s sh reports nothing.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
@myasnikovdaniil
myasnikovdaniil force-pushed the fix/seaweedfs-rename-fallout branch from a35ae9e to bd770b2 Compare July 20, 2026 10:42

@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

fix(seaweedfs) closes the 4.31 rename fallout on the 1.5.x→1.6 upgrade path. Zero CRITICAL / MAJOR / MINOR blocking findings. The one cozy-invariant that fired (charts-direct-edit, MINOR) is fully neutralised by the correct form. Each critical node was verified by execution (helm unittest, helm template renders, migration bats in busybox ash via docker, guard-parity/audit bats, canary), not just by reading.

Verified by execution

  • Migrations 43 (edit) / 53 (new) + targetVersion. main is at 52 (target 53); this PR adds 53 and bumps target to 54 (strictly greater), so run-migrations.sh seq CURRENT 53 genuinely re-runs the hand-over on already-upgraded clients. hack/check-migrations-target.sh confirms. Migration bats (hack/migration-seaweedfs-db-adopt.bats) 5/5 green on the key paths (default / non-default handover, mixed fleet, protect-missing-keep, repair-53); full 15-test set passes in the "Unit & controller tests" CI job.
  • Vendored-chart invariant handled correctly. Edits under packages/system/seaweedfs/charts/seaweedfs/templates/{cosi/cosi-cluster-role.yaml,shared/cluster-role.yaml} are captured in packages/system/seaweedfs/patches/cluster-scoped-names-per-namespace.patch, and packages/system/seaweedfs/Makefile:update re-applies it (patch -p4 < patches/…) — no silent revert on make update.
  • Cluster-scoped RBAC de-collision. extra/seaweedfs/templates/seaweedfs.yaml:226-228 sets global.serviceAccountName: "{{ .Release.Namespace }}-seaweedfs" on the emitted <name>-system HR; seaweedfs.compat folds it to the canonical key; cluster-scoped-rbac-guard.yaml asserts $sa == <namespace>-seaweedfs; tests/cluster_scoped_names_test.yaml confirms name uniqueness across two namespaces. The guard render does not fail.
  • Shell-portability trap did not fire. Migrations 43/53 are #!/bin/sh + set -euo pipefail; both production and the bats test run them through busybox ash (docker run alpine … /migrations/<n> by path, not sh <file> on a dash runner). CI green confirms.
  • Naming guard is fail-closed. Local render: fresh install renders normally, client-side/unittest is skipped (empty canary), the blind-upgrade canary (make test-guard-fail-closed) refuses as intended; class-S/MIXED is a deliberate refuse. Guard-parity bats confirms byte-identical detection blocks between system and extra.
  • Cleanup hook is safe (extra/seaweedfs/templates/hooks/cleanup.yaml): deletion of Cluster/seaweedfs-db is gated on topology != Client + RBAC resourceNames:[seaweedfs-db] + a runtime ownership recheck (fail-closed on foreign owner); PVCs by release-scoped label; --wait=false throughout; activeDeadlineSeconds: 120, backoffLimit: 3, and hook-delete-policy without hook-failed (a failed Job is kept for debugging).

Caveats (non-blocking)

  1. E2E upgrade not independently reproduced (hermetic review, no live cluster; the PR's "E2E Tests" job was still pending). Logic verified via renders and migration bats, but the real N-1 → N run rests on the author's report plus the pending CI job.
  2. Deliberate refuse for class-S/MIXED tenants: their <name>-system HR (remediation.retries: -1) will loop-fail until a manual duplicate resolution per the runbook (docs/operations/seaweedfs-431-rename-recovery.md). This is a documented safe trade-off (the alternative is data loss); the release note warns about it.
  3. This is the first PR to introduce a docker-daemon dependency in the "Unit & controller tests" job (migration bats). Confirmed green on the current runner, but it depends on docker being present on future runners.

@myasnikovdaniil
myasnikovdaniil merged commit f5227ee into main Jul 20, 2026
17 checks passed
@myasnikovdaniil
myasnikovdaniil deleted the fix/seaweedfs-rename-fallout branch July 20, 2026 12:34
@github-actions

Copy link
Copy Markdown

Created backport PR for release-1.5:

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin backport-3339-to-release-1.5
git worktree add --checkout .worktree/backport-3339-to-release-1.5 backport-3339-to-release-1.5
cd .worktree/backport-3339-to-release-1.5
git reset --hard HEAD^
git cherry-pick -x 8499b5f4a1779c9e4e588863ffadfa5a3b1ab901 ae93356ed179cf46fd6da6cc37c9763ced4d4c2f 47e24f01d1f3fccc8f35e7dd093fafb5d43d9ed2 601f164c5f02c43786c50ef42f36cced97d6fd4e 24bcb0bb6916766cb8cbcf0dc0f31b7a568da11c 95fcc76902b8444395c4f83aec71f96753e05f0a b16a51e60ebee5b751d360f8aae9e874823f9308 b4f98af940029e7e32ddbaaceafc402c7a41c23a a099b7e086c55365155770f2d2e399ad75c62677 8355f3e36fba333d137efdb8481e3e1a1e5c39dc 9b82d1bf36631acede467fa9a6eeb93523e7c285 bd770b2288ec5ac7a764f61708b6bb939e11d1ed
git push --force-with-lease

myasnikovdaniil added a commit that referenced this pull request Jul 31, 2026
Backport of #3339 onto release-1.5, on top of the #3282 adopt-in-place
fix (#3326).

Three differences from main, all forced by what release-1.5 does not
carry:

  * The repair migration is renumbered 53 -> 45 and stamps 46, since
    release-1.5 tops out at migration 44 with targetVersion 45.
    platform values.yaml goes to targetVersion 46.

  * Migrations 43 and 45 keep release-1.5's inline labelled stamp
    instead of lib/cozystack-version.sh. That helper is the shared
    stamp refactor (#2980), whose backport (#3065) was closed, so it
    does not exist on this branch. Only lib/seaweedfs-db-adopt.sh --
    the part that actually carries the fix -- is sourced.

  * The extra/seaweedfs post-delete cleanup hook and its three tests
    are dropped. The hook arrived with #3092, a breaking change that
    was never backported, so there is nothing here for #3339's changes
    to it to apply to. Comments in lib/seaweedfs-db-adopt.sh and the
    recovery runbook that promised the hook would reclaim an orphaned
    volume are corrected to say it does not on 1.5.x.

The migration renumbering is carried through the shared helper, the
bats suite (STAMP 54 -> 46) and the runbook prose.

Verified locally: helm unittest green for both seaweedfs charts (51 + 16
assertions) including both blind-upgrade guard canaries, and 34 bats
assertions across migration-seaweedfs-db-adopt, seaweedfs-naming-audit
and seaweedfs-guard-parity.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit 88e9fdcbbbff8aeab317b08557ca6bd6c8b5706d)
myasnikovdaniil added a commit that referenced this pull request Jul 31, 2026
Backport of #3339 onto release-1.5, on top of the #3282 adopt-in-place
fix (#3326).

Three differences from main, all forced by what release-1.5 does not
carry:

  * The repair migration is renumbered 53 -> 45 and stamps 46, since
    release-1.5 tops out at migration 44 with targetVersion 45.
    platform values.yaml goes to targetVersion 46.

  * Migrations 43 and 45 keep release-1.5's inline labelled stamp
    instead of lib/cozystack-version.sh. That helper is the shared
    stamp refactor (#2980), whose backport (#3065) was closed, so it
    does not exist on this branch. Only lib/seaweedfs-db-adopt.sh --
    the part that actually carries the fix -- is sourced.

  * The extra/seaweedfs post-delete cleanup hook and its three tests
    are dropped. The hook arrived with #3092, a breaking change that
    was never backported, so there is nothing here for #3339's changes
    to it to apply to. Comments in lib/seaweedfs-db-adopt.sh and the
    recovery runbook that promised the hook would reclaim an orphaned
    volume are corrected to say it does not on 1.5.x.

The migration renumbering is carried through the shared helper, the
bats suite (STAMP 54 -> 46) and the runbook prose.

Verified locally: helm unittest green for both seaweedfs charts (51 + 16
assertions) including both blind-upgrade guard canaries, and 34 bats
assertions across migration-seaweedfs-db-adopt, seaweedfs-naming-audit
and seaweedfs-guard-parity.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit 88e9fdcbbbff8aeab317b08557ca6bd6c8b5706d)
myasnikovdaniil pushed a commit that referenced this pull request Jul 31, 2026
Backfill tenant.cozystack.io/<ancestor> labels on existing tenant
namespaces so clusters heal on upgrade without waiting on each tenant
HelmRelease to re-reconcile (#2810/#2171).

Backport of migration 49 from #2912, adapted for release-1.5:
- migration file 49 -> 46. The existing backport PR #3191 renumbered it to
  45, but the SeaweedFS repair migration backported alongside it (#3339 /
  #3370) claims 45 too, so the two collide. Whichever of those two PRs
  merges second has to take 46; here the SeaweedFS one keeps 45 because it
  is what the 1.5.x line is being cut for.
- targetVersion 45 -> 47, covering both new migrations. #3191 alone bumps it
  to 46, and because the two backports agree on that value git merges them
  without a conflict — leaving targetVersion one short of the highest
  migration, which run-migrations.sh silently skips (its loop stops at
  TARGET-1). release-1.5 has no `migrations-target-check` to catch it; that
  guard arrived on main after v1.5.2 and was never backported.
- stamp via the inline labeled-ConfigMap apply used by release-1.5
  migrations, since lib/cozystack-version.sh does not exist on this branch

Best-effort and idempotent: kubectl label --overwrite adds only the listed
labels, and per-namespace failures are tolerated so a transient apiserver
error cannot abort the platform upgrade.

(cherry picked from commit 50f78ce)

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit aee9fa85b65d96dae1dff7e5044fd26ce710797e)
myasnikovdaniil added a commit that referenced this pull request Aug 4, 2026
…on the 1.5.x→1.6 upgrade path (#3370)

# Description
Backport of #3339 to `release-1.5`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) area/storage Issues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor) backport Should change be backported on previous release kind/bug Categorizes issue or PR as related to a bug size/XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants