feat: make storage:mount upsert the existing attachment

Re-running `storage:mount <app> <entry> --container-dir <path>` against an existing `(entry, container_dir, process_type)` tuple now updates the attachment's mount-time fields in place instead of erroring with `already mounted`. Declarative tooling that wants to change `--volume-options`, `--volume-chown`, `--phase`, `--volume-subpath`, or `--volume-readonly` on an existing mount no longer has to unmount-then-remount, which briefly dropped the volume from `storage:report` and raced against any deploy that fired in the window. The legacy `host:container[:opts]` form keeps its strict `Mount path already exists.` failure.
This commit is contained in:
Jose Diaz-Gonzalez
2026-05-30 03:23:32 -04:00
parent e9160db769
commit 3bb21b6987
5 changed files with 257 additions and 1 deletions

View File

@@ -193,7 +193,21 @@ func CommandMount(input CommandMountInput) error {
VolumeChown: input.VolumeChown,
}
return AddAttachment(input.AppName, attachment)
// Idempotent contract: same (entry, container_dir, process_type)
// tuple updates the mount-time fields in place rather than appending
// a duplicate attachment. Lets declarative tooling change
// --volume-options / --volume-chown / --phase / etc. without an
// unmount-then-remount dance that briefly drops the volume.
created, err := UpsertAttachment(input.AppName, attachment)
if err != nil {
return err
}
if created {
common.LogInfo1(fmt.Sprintf("Storage entry %s mounted at %s on %s", entry.Name, input.ContainerDir, input.AppName))
} else {
common.LogInfo1(fmt.Sprintf("Storage entry %s mount at %s on %s updated", entry.Name, input.ContainerDir, input.AppName))
}
return nil
}
// CommandUnmountInput captures the flags accepted by storage:unmount when