test: cover the legacy -v migration with go and bats tests

Adds plugins/storage/migrate_test.go exercising migrateApp and MigrateLegacyMounts: single deploy phase, cross-phase grouping into one attachment, :ro and free-form volume options preserved on the attachment, idempotency on a second pass, the per-app flag-file fast path that skips already-migrated apps, and the name-collision refusal that aborts before draining. tests/unit/storage.bats gains an end-to-end case that stages a -v line via docker-options:add, runs storage:migrate, and asserts the synthesized colon form shows up in storage:list, the legacy-<hash> entry shows up in storage:list-entries, and the original -v line is gone from docker-options:report on both phases. To make that test (and operators with restored backups) practical, storage gains a storage:migrate <app>|--all subcommand backed by a new MigrateApp helper that skips the per-app flag file so a re-run actually re-scans.
This commit is contained in:
Jose Diaz-Gonzalez
2026-04-30 02:37:01 -04:00
parent e9a53ac82d
commit f0d088bc1d
8 changed files with 446 additions and 1 deletions

View File

@@ -323,6 +323,44 @@ func stdinModes() (interactive bool, tty bool) {
return interactive, tty
}
// CommandMigrate re-runs the legacy `-v` → entry/attachment migration
// for a single app or every app on the install. Useful when an operator
// has restored an app from a backup that predates the install-time
// migration, or when they've added `-v` lines via docker-options:add
// after the per-app flag was set.
func CommandMigrate(appName string, all bool) error {
if all && appName != "" {
return errors.New("storage:migrate accepts either an app name or --all, not both")
}
if !all && appName == "" {
return errors.New("storage:migrate requires an app name (or --all)")
}
if all {
apps, err := common.DokkuApps()
if err != nil {
if errors.Is(err, common.NoAppsExist) {
return nil
}
return err
}
for _, app := range apps {
if err := MigrateApp(app); err != nil {
return err
}
}
common.LogInfo1(fmt.Sprintf("Re-migrated %d app(s)", len(apps)))
return nil
}
if err := common.VerifyAppName(appName); err != nil {
return err
}
if err := MigrateApp(appName); err != nil {
return err
}
common.LogInfo1(fmt.Sprintf("Re-migrated %s", appName))
return nil
}
// CommandWait blocks until a k3s storage entry's PVC is bound.
func CommandWait(name string) error {
if !EntryExists(name) {