Honor label filters for volume prune when all is set

NormalizeVolumePruneFilters discarded every query filter when the "all"
pseudo-filter was set, deleting label/label!/until before they reached the
volume filter generator. As a result `podman volume prune --all --filter
label=foo` ignored the label and pruned every unused volume.

"all" only widens the prune scope from anonymous-only to all unused volumes;
it is orthogonal to the label filters, which must still select which of those
volumes are removed. Drop only the "all" key and keep the remaining filters so
they continue to apply.

NormalizeVolumePruneFilters is shared by the local (abi), remote (libpod API),
and Docker-compat prune paths, so all three were affected.

Signed-off-by: Shuai Yuan <shuaiyuanzju@gmail.com>
This commit is contained in:
Shuai Yuan 2026-06-25 17:12:20 +08:00
parent d3687aba7f
commit 4586dc2c60
4 changed files with 44 additions and 3 deletions

View file

@ -21,6 +21,8 @@ removal unless **--force** is used.
Remove all unused volumes (anonymous and named). Without this option, only anonymous unused volumes are removed.
**--all** can be combined with **--filter**: **--all** widens the set of candidate volumes to all unused ones, and the filters then restrict which of those are removed. For example, **--all --filter label!=keep** removes every unused volume that does not have the *keep* label.
#### **--dry-run**
Show which volumes would be pruned without removing them.
@ -77,6 +79,11 @@ Prune all unused volumes (anonymous and named).
$ podman volume prune --all --force
```
Prune all unused volumes except those with a specific label (combine **--all** with a filter).
```
$ podman volume prune --all --force --filter label!=keep
```
Prune all unused volumes using the filter (equivalent to **--all**).
```
$ podman volume prune --filter all=true --force

View file

@ -66,9 +66,12 @@ func NormalizeVolumePruneFilters(f url.Values) url.Values {
}
allValue := f.Get("all")
if strings.EqualFold(allValue, "true") || allValue == "1" {
for key := range f {
f.Del(key)
}
// "all" only widens the scope from anonymous-only to every unused
// volume; it is orthogonal to the other filters. Drop just the "all"
// key and keep the rest (e.g. label/label!, until) so they continue to
// constrain which volumes are pruned, matching Docker, which ANDs the
// "all" scope together with the label filters.
f.Del("all")
return f
}
f.Del("all")

View file

@ -96,6 +96,19 @@ func TestNormalizeVolumePruneFilters(t *testing.T) {
t.Fatalf("got %#v, want no all/anonymous keys", got)
}
})
t.Run("all true keeps label filters", func(t *testing.T) {
t.Parallel()
got := NormalizeVolumePruneFilters(url.Values{"all": {"true"}, "label": {"k=v"}, "label!": {"x"}})
if got.Has("all") || got.Has("anonymous") {
t.Fatalf("got %#v, want no all/anonymous keys", got)
}
if got.Get("label") != "k=v" {
t.Fatalf("label dropped alongside all: got %#v", got)
}
if got.Get("label!") != "x" {
t.Fatalf("label! dropped alongside all: got %#v", got)
}
})
t.Run("label without anonymous key does not inject anonymous", func(t *testing.T) {
t.Parallel()
in := url.Values{"label": {"k=v"}}

View file

@ -110,6 +110,24 @@ var _ = Describe("Podman volume prune", func() {
podmanTest.PodmanExitCleanly("volume", "prune", "--force", "--filter", "label!=testlabel")
})
It("podman volume prune --all keeps the label filter", func() {
// Regression: the "all" flag must not discard a label/label! filter.
// With "--all --filter label!=keep" only unlabeled unused volumes are
// pruned; the labeled one survives. Before the fix, "all" cleared the
// filter and both volumes were removed.
podmanTest.PodmanExitCleanly("volume", "create", "--label", "keep=yes", "keepvol")
podmanTest.PodmanExitCleanly("volume", "create", "dropvol")
session := podmanTest.PodmanExitCleanly("volume", "ls", "-q")
Expect(session.OutputToStringArray()).To(HaveLen(2))
podmanTest.PodmanExitCleanly("volume", "prune", "--all", "--force", "--filter", "label!=keep")
session = podmanTest.PodmanExitCleanly("volume", "ls", "-q")
Expect(session.OutputToStringArray()).To(HaveLen(1))
Expect(session.OutputToString()).To(Equal("keepvol"))
})
It("podman system prune --volume", func() {
useCustomNetworkDir(podmanTest, tempdir)
podmanTest.PodmanExitCleanly("volume", "create")