Read volume sizes only when asked and remove in bounded batches, so docker_volumes_remove_anonymous answers on a machine with over a thousand volumes (issue 1796)
mesh/merge-gate building on shanks since 00:21
mesh/delivery superseded: a newer head of the same pull request

This commit is contained in:
2026-10-22 00:20:32 +02:00
parent a3794ea156
commit bfc42e4f4f
4 changed files with 98 additions and 24 deletions
+8 -2
View File
@@ -291,12 +291,14 @@ func tools(c *Client) []stdio.Tool {
Name: "docker_volumes_remove_anonymous",
Description: "Remove the leftover anonymous volumes (novox/hq issue 1796): only a volume docker labels anonymous (as docker_volumes says), which no container mounts in any state " +
"and so none the mesh holds, and with created_before only one created before it. Every other volume is answered with why it stays; a named volume is data and is never taken, " +
"and no volume can be named. Each is removed on its own, never forced; the answer has the count and names removed. The answer has each volume's size and the bytes freed (or, in a dry run, that would be). " +
"and no volume can be named. Each is removed on its own, never forced; the answer has the count and names removed. With sizes true the answer has each volume's size and the bytes freed; docker's size listing is slow on many volumes, so it is asked for, and when it does not answer the call answers without it. At most limit are removed per call, in batches. " +
"A dry run by default; dry_run false needs why. Replaces docker volume rm and docker volume prune. (a)",
Input: map[string]any{
"dry_run": map[string]any{"type": "boolean", "description": "list only (default true)"},
"why": map[string]any{"type": "string", "description": "why: required when dry_run is false"},
"created_before": map[string]any{"type": "string", "description": "RFC 3339 time: only volumes created before it (default: any age)"},
"sizes": map[string]any{"type": "boolean", "description": "also read each volume's size and the bytes freed (default false: docker walks every volume for it and on a machine with very many may not answer; the call then answers without sizes)"},
"limit": map[string]any{"type": "integer", "description": "how many to remove in one call (default 200, at most 2000); the answer says how many remain, call again for them"},
},
Run: func(args map[string]any) (any, error) {
for _, k := range []string{"name", "names", "volume", "volumes"} {
@@ -304,7 +306,11 @@ func tools(c *Client) []stdio.Tool {
return nil, fmt.Errorf("%s: this tool takes no volume by name; it removes only unmounted anonymous volumes", k)
}
}
ask := RemoveVolumesAsk{DryRun: flag(args, "dry_run", true), Why: optional(args, "why")}
limit, err := bounded(args, "limit", 200, 2000)
if err != nil {
return nil, err
}
ask := RemoveVolumesAsk{DryRun: flag(args, "dry_run", true), Why: optional(args, "why"), Sizes: flag(args, "sizes", false), Limit: limit}
if s := optional(args, "created_before"); s != "" {
t, err := time.Parse(time.RFC3339, s)
if err != nil {