Skip to content

Commit 0dec5d2

Browse files
committed
volumes: prune: add --all / -a option
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent d4f2609 commit 0dec5d2

10 files changed

Lines changed: 117 additions & 22 deletions

File tree

cli/command/volume/prune.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ import (
88
"github.com/docker/cli/cli/command"
99
"github.com/docker/cli/cli/command/completion"
1010
"github.com/docker/cli/opts"
11+
"github.com/docker/docker/api/types/versions"
12+
"github.com/docker/docker/errdefs"
1113
units "github.com/docker/go-units"
14+
"github.com/pkg/errors"
1215
"github.com/spf13/cobra"
1316
)
1417

1518
type pruneOptions struct {
19+
all bool
1620
force bool
1721
filter opts.FilterOpt
1822
}
@@ -41,18 +45,37 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
4145
}
4246

4347
flags := cmd.Flags()
48+
flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused volumes, not just anonymous ones")
49+
flags.SetAnnotation("all", "version", []string{"1.42"})
4450
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
4551
flags.Var(&options.filter, "filter", `Provide filter values (e.g. "label=<label>")`)
4652

4753
return cmd
4854
}
4955

50-
const warning = `WARNING! This will remove all local volumes not used by at least one container.
56+
const (
57+
unusedVolumesWarning = `WARNING! This will remove anonymous local volumes not used by at least one container.
5158
Are you sure you want to continue?`
59+
allVolumesWarning = `WARNING! This will remove all local volumes not used by at least one container.
60+
Are you sure you want to continue?`
61+
)
5262

5363
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
5464
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
5565

66+
warning := unusedVolumesWarning
67+
if versions.GreaterThanOrEqualTo(dockerCli.CurrentVersion(), "1.42") {
68+
if options.all {
69+
if pruneFilters.Contains("all") {
70+
return 0, "", errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --all and --filter all=1"))
71+
}
72+
pruneFilters.Add("all", "true")
73+
warning = allVolumesWarning
74+
}
75+
} else {
76+
// API < v1.42 removes all volumes (anonymous and named) by default.
77+
warning = allVolumesWarning
78+
}
5679
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
5780
return 0, "", nil
5881
}

cli/command/volume/prune_test.go

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,26 @@ import (
1313
"github.com/docker/docker/api/types/filters"
1414
"github.com/pkg/errors"
1515
"gotest.tools/v3/assert"
16+
is "gotest.tools/v3/assert/cmp"
1617
"gotest.tools/v3/golden"
1718
"gotest.tools/v3/skip"
1819
)
1920

2021
func TestVolumePruneErrors(t *testing.T) {
2122
testCases := []struct {
23+
name string
2224
args []string
2325
flags map[string]string
2426
volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error)
2527
expectedError string
2628
}{
2729
{
30+
name: "accepts no arguments",
2831
args: []string{"foo"},
2932
expectedError: "accepts no argument",
3033
},
3134
{
35+
name: "forced but other error",
3236
flags: map[string]string{
3337
"force": "true",
3438
},
@@ -37,20 +41,75 @@ func TestVolumePruneErrors(t *testing.T) {
3741
},
3842
expectedError: "error pruning volumes",
3943
},
44+
{
45+
name: "conflicting options",
46+
flags: map[string]string{
47+
"all": "true",
48+
"filter": "all=1",
49+
},
50+
expectedError: "conflicting options: cannot specify both --all and --filter all=1",
51+
},
52+
}
53+
for _, tc := range testCases {
54+
tc := tc
55+
t.Run(tc.name, func(t *testing.T) {
56+
cmd := NewPruneCommand(
57+
test.NewFakeCli(&fakeClient{
58+
volumePruneFunc: tc.volumePruneFunc,
59+
}),
60+
)
61+
cmd.SetArgs(tc.args)
62+
for key, value := range tc.flags {
63+
cmd.Flags().Set(key, value)
64+
}
65+
cmd.SetOut(io.Discard)
66+
cmd.SetErr(io.Discard)
67+
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
68+
})
69+
}
70+
}
71+
72+
func TestVolumePruneSuccess(t *testing.T) {
73+
testCases := []struct {
74+
name string
75+
args []string
76+
volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error)
77+
}{
78+
{
79+
name: "all",
80+
args: []string{"--all"},
81+
volumePruneFunc: func(pruneFilter filters.Args) (types.VolumesPruneReport, error) {
82+
assert.Check(t, is.Equal([]string{"true"}, pruneFilter.Get("all")))
83+
return types.VolumesPruneReport{}, nil
84+
},
85+
},
86+
{
87+
name: "all-forced",
88+
args: []string{"--all", "--force"},
89+
volumePruneFunc: func(pruneFilter filters.Args) (types.VolumesPruneReport, error) {
90+
return types.VolumesPruneReport{}, nil
91+
},
92+
},
93+
{
94+
name: "label-filter",
95+
args: []string{"--filter", "label=foobar"},
96+
volumePruneFunc: func(pruneFilter filters.Args) (types.VolumesPruneReport, error) {
97+
assert.Check(t, is.Equal([]string{"foobar"}, pruneFilter.Get("label")))
98+
return types.VolumesPruneReport{}, nil
99+
},
100+
},
40101
}
41102
for _, tc := range testCases {
42-
cmd := NewPruneCommand(
43-
test.NewFakeCli(&fakeClient{
44-
volumePruneFunc: tc.volumePruneFunc,
45-
}),
46-
)
47-
cmd.SetArgs(tc.args)
48-
for key, value := range tc.flags {
49-
cmd.Flags().Set(key, value)
50-
}
51-
cmd.SetOut(io.Discard)
52-
cmd.SetErr(io.Discard)
53-
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
103+
tc := tc
104+
t.Run(tc.name, func(t *testing.T) {
105+
cli := test.NewFakeCli(&fakeClient{volumePruneFunc: tc.volumePruneFunc})
106+
cmd := NewPruneCommand(cli)
107+
cmd.SetOut(io.Discard)
108+
cmd.SetArgs(tc.args)
109+
err := cmd.Execute()
110+
assert.NilError(t, err)
111+
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("volume-prune-success.%s.golden", tc.name))
112+
})
54113
}
55114
}
56115

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
WARNING! This will remove all local volumes not used by at least one container.
1+
WARNING! This will remove anonymous local volumes not used by at least one container.
22
Are you sure you want to continue? [y/N] Total reclaimed space: 0B
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Total reclaimed space: 0B
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
WARNING! This will remove all local volumes not used by at least one container.
2+
Are you sure you want to continue? [y/N] Total reclaimed space: 0B
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
WARNING! This will remove anonymous local volumes not used by at least one container.
2+
Are you sure you want to continue? [y/N] Total reclaimed space: 0B

cli/command/volume/testdata/volume-prune-yes.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
WARNING! This will remove all local volumes not used by at least one container.
1+
WARNING! This will remove anonymous local volumes not used by at least one container.
22
Are you sure you want to continue? [y/N] Deleted Volumes:
33
foo
44
bar

contrib/completion/bash/docker

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5383,7 +5383,7 @@ _docker_volume_prune() {
53835383

53845384
case "$cur" in
53855385
-*)
5386-
COMPREPLY=( $( compgen -W "--filter --force -f --help" -- "$cur" ) )
5386+
COMPREPLY=( $( compgen -W "--all -a --filter --force -f --help" -- "$cur" ) )
53875387
;;
53885388
esac
53895389
}

contrib/completion/zsh/_docker

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2527,6 +2527,8 @@ __docker_volume_subcommand() {
25272527
(prune)
25282528
_arguments $(__docker_arguments) \
25292529
$opts_help \
2530+
"($help -a --all)"{-a,--all}"[Remove all unused local volumes, not just anonymous ones]" \
2531+
"($help)*--filter=[Filter values]:filter:__docker_complete_prune_filters" \
25302532
"($help -f --force)"{-f,--force}"[Do not prompt for confirmation]" && ret=0
25312533
;;
25322534
(rm)

docs/reference/commandline/volume_prune.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,26 @@ Remove all unused local volumes
55

66
### Options
77

8-
| Name | Type | Default | Description |
9-
|:----------------------|:---------|:--------|:---------------------------------------------|
10-
| [`--filter`](#filter) | `filter` | | Provide filter values (e.g. `label=<label>`) |
11-
| `-f`, `--force` | | | Do not prompt for confirmation |
8+
| Name | Type | Default | Description |
9+
|:------------------------------|:---------|:--------|:---------------------------------------------------|
10+
| [`-a`](#all), [`--all`](#all) | | | Remove all unused volumes, not just anonymous ones |
11+
| [`--filter`](#filter) | `filter` | | Provide filter values (e.g. `label=<label>`) |
12+
| `-f`, `--force` | | | Do not prompt for confirmation |
1213

1314

1415
<!---MARKER_GEN_END-->
1516

1617
## Description
1718

18-
Remove all unused local volumes. Unused local volumes are those which are not referenced by any containers
19+
Remove all unused local volumes. Unused local volumes are those which are not
20+
referenced by any containers. By default, it only removes anonymous volumes.
1921

2022
## Examples
2123

2224
```console
2325
$ docker volume prune
2426

25-
WARNING! This will remove all local volumes not used by at least one container.
27+
WARNING! This will remove anonymous local volumes not used by at least one container.
2628
Are you sure you want to continue? [y/N] y
2729
Deleted Volumes:
2830
07c7bdf3e34ab76d921894c2b834f073721fccfbbcba792aa7648e3a7a664c2e
@@ -31,6 +33,10 @@ my-named-vol
3133
Total reclaimed space: 36 B
3234
```
3335

36+
### <a name="all"></a> Filtering (--all, -a)
37+
38+
Use the `--all` flag to prune both unused anonymous and named volumes.
39+
3440
### <a name="filter"></a> Filtering (--filter)
3541

3642
The filtering flag (`--filter`) format is of "key=value". If there is more

0 commit comments

Comments
 (0)