Skip to content

Commit

Permalink
volumes: prune: add --all / -a option
Browse files Browse the repository at this point in the history
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
  • Loading branch information
thaJeztah committed Apr 20, 2023
1 parent d4f2609 commit 01f2f71
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 22 deletions.
25 changes: 24 additions & 1 deletion cli/command/volume/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/errdefs"
units "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

type pruneOptions struct {
all bool
force bool
filter opts.FilterOpt
}
Expand Down Expand Up @@ -41,18 +45,37 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
}

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

return cmd
}

const warning = `WARNING! This will remove all local volumes not used by at least one container.
const (
unusedVolumesWarning = `WARNING! This will remove all local anonymous volumes not used by at least one container.
Are you sure you want to continue?`
allVolumesWarning = `WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue?`
)

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

warning := unusedVolumesWarning
if versions.GreaterThanOrEqualTo(dockerCli.CurrentVersion(), "1.42") {
if options.all {
if pruneFilters.Contains("all") {
return 0, "", errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --all and --filter all=1"))
}
pruneFilters.Add("all", "true")
warning = allVolumesWarning
}
} else {
// API < v1.42 removes all volumes (anonymous and named) by default.
warning = allVolumesWarning
}
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
return 0, "", nil
}
Expand Down
83 changes: 71 additions & 12 deletions cli/command/volume/prune_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,26 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
"gotest.tools/v3/skip"
)

func TestVolumePruneErrors(t *testing.T) {
testCases := []struct {
name string
args []string
flags map[string]string
volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error)
expectedError string
}{
{
name: "accepts no arguments",
args: []string{"foo"},
expectedError: "accepts no argument",
},
{
name: "forced but other error",
flags: map[string]string{
"force": "true",
},
Expand All @@ -37,20 +41,75 @@ func TestVolumePruneErrors(t *testing.T) {
},
expectedError: "error pruning volumes",
},
{
name: "conflicting options",
flags: map[string]string{
"all": "true",
"filter": "all=1",
},
expectedError: "conflicting options: cannot specify both --all and --filter all=1",
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cmd := NewPruneCommand(
test.NewFakeCli(&fakeClient{
volumePruneFunc: tc.volumePruneFunc,
}),
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
}
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
})
}
}

func TestVolumePruneSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error)
}{
{
name: "all",
args: []string{"--all"},
volumePruneFunc: func(pruneFilter filters.Args) (types.VolumesPruneReport, error) {
assert.Check(t, is.Equal([]string{"true"}, pruneFilter.Get("all")))
return types.VolumesPruneReport{}, nil
},
},
{
name: "all-forced",
args: []string{"--all", "--force"},
volumePruneFunc: func(pruneFilter filters.Args) (types.VolumesPruneReport, error) {
return types.VolumesPruneReport{}, nil
},
},
{
name: "label-filter",
args: []string{"--filter", "label=foobar"},
volumePruneFunc: func(pruneFilter filters.Args) (types.VolumesPruneReport, error) {
assert.Check(t, is.Equal([]string{"foobar"}, pruneFilter.Get("label")))
return types.VolumesPruneReport{}, nil
},
},
}
for _, tc := range testCases {
cmd := NewPruneCommand(
test.NewFakeCli(&fakeClient{
volumePruneFunc: tc.volumePruneFunc,
}),
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
}
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
tc := tc
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{volumePruneFunc: tc.volumePruneFunc})
cmd := NewPruneCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NilError(t, err)
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("volume-prune-success.%s.golden", tc.name))
})
}
}

Expand Down
2 changes: 1 addition & 1 deletion cli/command/volume/testdata/volume-prune-no.golden
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
WARNING! This will remove all local volumes not used by at least one container.
WARNING! This will remove all local anonymous volumes not used by at least one container.
Are you sure you want to continue? [y/N] Total reclaimed space: 0B
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Total reclaimed space: 0B
2 changes: 2 additions & 0 deletions cli/command/volume/testdata/volume-prune-success.all.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] Total reclaimed space: 0B
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
WARNING! This will remove all local anonymous volumes not used by at least one container.
Are you sure you want to continue? [y/N] Total reclaimed space: 0B
2 changes: 1 addition & 1 deletion cli/command/volume/testdata/volume-prune-yes.golden
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
WARNING! This will remove all local volumes not used by at least one container.
WARNING! This will remove all local anonymous volumes not used by at least one container.
Are you sure you want to continue? [y/N] Deleted Volumes:
foo
bar
Expand Down
2 changes: 1 addition & 1 deletion contrib/completion/bash/docker
Original file line number Diff line number Diff line change
Expand Up @@ -5383,7 +5383,7 @@ _docker_volume_prune() {

case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--filter --force -f --help" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--all -a --filter --force -f --help" -- "$cur" ) )
;;
esac
}
Expand Down
2 changes: 2 additions & 0 deletions contrib/completion/zsh/_docker
Original file line number Diff line number Diff line change
Expand Up @@ -2527,6 +2527,8 @@ __docker_volume_subcommand() {
(prune)
_arguments $(__docker_arguments) \
$opts_help \
"($help -a --all)"{-a,--all}"[Remove all unused volumes, not just anonymous ones]" \
"($help)*--filter=[Filter values]:filter:__docker_complete_prune_filters" \
"($help -f --force)"{-f,--force}"[Do not prompt for confirmation]" && ret=0
;;
(rm)
Expand Down
18 changes: 12 additions & 6 deletions docs/reference/commandline/volume_prune.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,26 @@ Remove all unused local volumes

### Options

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


<!---MARKER_GEN_END-->

## Description

Remove all unused local volumes. Unused local volumes are those which are not referenced by any containers
Remove all unused local volumes. Unused local volumes are those which are not
referenced by any containers. By default, it only removes anonymous volumes.

## Examples

```console
$ docker volume prune

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

### <a name="all"></a> Filtering (--all, -a)

Use the `--all` flag to prune both unused anonymous and named volumes.

### <a name="filter"></a> Filtering (--filter)

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

0 comments on commit 01f2f71

Please sign in to comment.