mirror of https://github.com/docker/cli.git
Merge pull request #4218 from thaJeztah/volumes_prune_all
volumes: prune: add --all / -a option
This commit is contained in:
commit
26a73575f1
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,8 +41,18 @@ 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,
|
||||
|
@ -51,6 +65,51 @@ func TestVolumePruneErrors(t *testing.T) {
|
|||
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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Total reclaimed space: 0B
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -6,7 +6,8 @@ Remove all unused local volumes
|
|||
### Options
|
||||
|
||||
| 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 |
|
||||
|
||||
|
@ -15,14 +16,15 @@ Remove all unused local volumes
|
|||
|
||||
## 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
|
||||
|
|
Loading…
Reference in New Issue