Add a whole-set form for an app's storage attachments
Description of feature
There is no way to declare the complete set of storage attachments an app should have. storage:mount upserts a single attachment and storage:unmount removes a single one, and the comment on the upsert states the contract plainly:
// Idempotent contract: same (entry, container_dir, process_type)
// tuple updates the mount-time fields in place rather than appending
// a duplicate attachment.
created, err := UpsertAttachment(input.AppName, attachment)There is no storage:mounts:set, no --replace on storage:mount, and no plugn trigger that rewrites the attachment set wholesale - the storage triggers are limited to install, post-delete, post-app-clone-setup, post-app-rename-setup, storage-list, storage-app-mounts, and docker-args.
So a caller converging an app onto a declared set of mounts must read storage:list <app> --format json (or storage:report), diff it against the desired set, and issue one storage:mount or storage:unmount per difference. That is N non-atomic invocations, and a failure partway through leaves the app with a mixture of the old and new attachments - which for storage means a container that may mount the wrong volumes on its next start.
The request is a whole-set form, so an app's attachments can be replaced in one call. Dokku already has this shape in adjacent plugins and it is worth matching one of them rather than inventing a third: ports:set and buildpacks:set --replace both write the full list through common.PropertyListWrite and reject an empty argument list, deferring the empty case to a separate :clear command. Attachments are richer than a flat list - each carries a container dir, process type, and mount-time fields - so the argument shape needs some thought, and a stdin-fed form taking the same JSON that storage:list --format json emits may read better than positional varargs.
Validating every attachment before writing any of them, the way buildpacks:set --replace validates all URLs before the write, would be the useful half of this even if the command shape ends up different.
Source: dokku/dokku