feat: storage attachments, docker-args triggers, install-time migration

storage:mount and storage:unmount now accept the named-entry form (--container-dir, --phase, --process-type, --volume-subpath, --volume-readonly, --volume-chown), persisting attachments via the property list. The legacy host:container colon form is preserved for back-compat. Storage now emits its own docker-args-deploy and docker-args-run triggers so docker-local apps continue to receive bind mounts even after their data moves out of docker-options. The install trigger walks every app once per upgrade and converts legacy -v lines into legacy-<hash> entries plus attachments, draining the originals from docker-options last so a partial run leaves the old behavior intact. Lifecycle triggers post-delete, post-app-clone-setup, and post-app-rename-setup keep the attachment store in sync with app state.
This commit is contained in:
Jose Diaz-Gonzalez
2026-04-29 14:43:51 -04:00
parent 8a46d89181
commit e1b6bdb1d4
6 changed files with 453 additions and 30 deletions

View File

@@ -86,10 +86,23 @@ func main() {
err = storage.CommandListEntries(*scheduler, *format)
case "mount":
args := flag.NewFlagSet("storage:mount", flag.ExitOnError)
containerDir := args.String("container-dir", "", "--container-dir: container path (named-entry form)")
phases := args.StringSlice("phase", nil, "--phase: phase to mount in (deploy, run; default both)")
processType := args.String("process-type", "", "--process-type: process type to mount for")
subpath := args.String("volume-subpath", "", "--volume-subpath: subpath within the entry")
readonly := args.Bool("volume-readonly", false, "--volume-readonly: mount the volume read-only")
volumeChown := args.String("volume-chown", "", "--volume-chown: chown option applied at mount time")
args.Parse(os.Args[2:])
appName := args.Arg(0)
mountPath := args.Arg(1)
err = storage.CommandMount(appName, mountPath)
err = storage.CommandMount(storage.CommandMountInput{
AppName: args.Arg(0),
NameOrPath: args.Arg(1),
ContainerDir: *containerDir,
Phases: *phases,
ProcessType: *processType,
Subpath: *subpath,
Readonly: *readonly,
VolumeChown: *volumeChown,
})
case "report":
args := flag.NewFlagSet("storage:report", flag.ExitOnError)
format := args.String("format", "stdout", "format: [ stdout | json ]")
@@ -104,10 +117,13 @@ func main() {
}
case "unmount":
args := flag.NewFlagSet("storage:unmount", flag.ExitOnError)
containerDir := args.String("container-dir", "", "--container-dir: container path (named-entry form, disambiguates duplicates)")
args.Parse(os.Args[2:])
appName := args.Arg(0)
mountPath := args.Arg(1)
err = storage.CommandUnmount(appName, mountPath)
err = storage.CommandUnmount(storage.CommandUnmountInput{
AppName: args.Arg(0),
NameOrPath: args.Arg(1),
ContainerDir: *containerDir,
})
default:
err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand)
}

View File

@@ -24,6 +24,27 @@ func main() {
phase := flag.Arg(1)
format := flag.Arg(2)
err = storage.TriggerStorageList(appName, phase, format)
case "storage-app-mounts":
appName := flag.Arg(0)
phase := flag.Arg(1)
err = storage.TriggerStorageAppMounts(appName, phase)
case "docker-args-deploy":
appName := flag.Arg(0)
err = storage.TriggerDockerArgs(appName, storage.PhaseDeploy)
case "docker-args-run":
appName := flag.Arg(0)
err = storage.TriggerDockerArgs(appName, storage.PhaseRun)
case "post-delete":
appName := flag.Arg(0)
err = storage.TriggerPostDelete(appName)
case "post-app-clone-setup":
oldName := flag.Arg(0)
newName := flag.Arg(1)
err = storage.TriggerPostAppCloneSetup(oldName, newName)
case "post-app-rename-setup":
oldName := flag.Arg(0)
newName := flag.Arg(1)
err = storage.TriggerPostAppRenameSetup(oldName, newName)
default:
err = fmt.Errorf("Invalid plugin trigger call: %s", trigger)
}