Merge pull request #4229 from thaJeztah/23.0_backport_volumes_prune_all

[23.0 backport] volumes: prune: add --all / -a option
This commit is contained in:
Sebastiaan van Stijn 2023-04-26 13:00:58 +02:00 committed by GitHub
commit 776388cc19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 121 additions and 21 deletions

View File

@ -50,6 +50,7 @@ func TestVolumeCreateErrors(t *testing.T) {
cmd.Flags().Set(key, value)
}
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}

View File

@ -62,6 +62,7 @@ func TestVolumeInspectErrors(t *testing.T) {
cmd.Flags().Set(key, value)
}
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}

View File

@ -43,6 +43,7 @@ func TestVolumeListErrors(t *testing.T) {
cmd.Flags().Set(key, value)
}
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}

View File

@ -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
}
@ -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 anonymous local 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
}

View File

@ -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",
},
@ -37,19 +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 {
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)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
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 {
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))
})
}
}

View File

@ -33,6 +33,7 @@ func TestVolumeRemoveErrors(t *testing.T) {
}))
cmd.SetArgs(tc.args)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}

View File

@ -1,2 +1,2 @@
WARNING! This will remove all local volumes not used by at least one container.
WARNING! This will remove anonymous local volumes not used by at least one container.
Are you sure you want to continue? [y/N] Total reclaimed space: 0B

View File

@ -0,0 +1 @@
Total reclaimed space: 0B

View File

@ -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

View File

@ -0,0 +1,2 @@
WARNING! This will remove anonymous local volumes not used by at least one container.
Are you sure you want to continue? [y/N] Total reclaimed space: 0B

View File

@ -1,4 +1,4 @@
WARNING! This will remove all local volumes not used by at least one container.
WARNING! This will remove anonymous local volumes not used by at least one container.
Are you sure you want to continue? [y/N] Deleted Volumes:
foo
bar

View File

@ -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
}

View File

@ -2527,6 +2527,8 @@ __docker_volume_subcommand() {
(prune)
_arguments $(__docker_arguments) \
$opts_help \
"($help -a --all)"{-a,--all}"[Remove all unused local 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)

View File

@ -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), [`--all`](#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 anonymous local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
07c7bdf3e34ab76d921894c2b834f073721fccfbbcba792aa7648e3a7a664c2e
@ -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