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"
|
||||||
"github.com/docker/cli/cli/command/completion"
|
"github.com/docker/cli/cli/command/completion"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
|
"github.com/docker/docker/api/types/versions"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pruneOptions struct {
|
type pruneOptions struct {
|
||||||
|
all bool
|
||||||
force bool
|
force bool
|
||||||
filter opts.FilterOpt
|
filter opts.FilterOpt
|
||||||
}
|
}
|
||||||
|
@ -41,18 +45,37 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
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.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||||
flags.Var(&options.filter, "filter", `Provide filter values (e.g. "label=<label>")`)
|
flags.Var(&options.filter, "filter", `Provide filter values (e.g. "label=<label>")`)
|
||||||
|
|
||||||
return cmd
|
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?`
|
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) {
|
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
||||||
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
|
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) {
|
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||||
return 0, "", nil
|
return 0, "", nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,22 +13,26 @@ import (
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
|
is "gotest.tools/v3/assert/cmp"
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
"gotest.tools/v3/skip"
|
"gotest.tools/v3/skip"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestVolumePruneErrors(t *testing.T) {
|
func TestVolumePruneErrors(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
name string
|
||||||
args []string
|
args []string
|
||||||
flags map[string]string
|
flags map[string]string
|
||||||
volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error)
|
volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error)
|
||||||
expectedError string
|
expectedError string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
name: "accepts no arguments",
|
||||||
args: []string{"foo"},
|
args: []string{"foo"},
|
||||||
expectedError: "accepts no argument",
|
expectedError: "accepts no argument",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: "forced but other error",
|
||||||
flags: map[string]string{
|
flags: map[string]string{
|
||||||
"force": "true",
|
"force": "true",
|
||||||
},
|
},
|
||||||
|
@ -37,20 +41,75 @@ func TestVolumePruneErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectedError: "error pruning volumes",
|
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 {
|
for _, tc := range testCases {
|
||||||
cmd := NewPruneCommand(
|
tc := tc
|
||||||
test.NewFakeCli(&fakeClient{
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
volumePruneFunc: tc.volumePruneFunc,
|
cmd := NewPruneCommand(
|
||||||
}),
|
test.NewFakeCli(&fakeClient{
|
||||||
)
|
volumePruneFunc: tc.volumePruneFunc,
|
||||||
cmd.SetArgs(tc.args)
|
}),
|
||||||
for key, value := range tc.flags {
|
)
|
||||||
cmd.Flags().Set(key, value)
|
cmd.SetArgs(tc.args)
|
||||||
}
|
for key, value := range tc.flags {
|
||||||
cmd.SetOut(io.Discard)
|
cmd.Flags().Set(key, value)
|
||||||
cmd.SetErr(io.Discard)
|
}
|
||||||
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
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
|
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:
|
Are you sure you want to continue? [y/N] Deleted Volumes:
|
||||||
foo
|
foo
|
||||||
bar
|
bar
|
||||||
|
|
|
@ -5383,7 +5383,7 @@ _docker_volume_prune() {
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
COMPREPLY=( $( compgen -W "--filter --force -f --help" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "--all -a --filter --force -f --help" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
|
@ -2527,6 +2527,8 @@ __docker_volume_subcommand() {
|
||||||
(prune)
|
(prune)
|
||||||
_arguments $(__docker_arguments) \
|
_arguments $(__docker_arguments) \
|
||||||
$opts_help \
|
$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
|
"($help -f --force)"{-f,--force}"[Do not prompt for confirmation]" && ret=0
|
||||||
;;
|
;;
|
||||||
(rm)
|
(rm)
|
||||||
|
|
|
@ -5,24 +5,26 @@ Remove all unused local volumes
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:----------------------|:---------|:--------|:---------------------------------------------|
|
|:------------------------------|:---------|:--------|:---------------------------------------------------|
|
||||||
| [`--filter`](#filter) | `filter` | | Provide filter values (e.g. `label=<label>`) |
|
| [`-a`](#all), [`--all`](#all) | | | Remove all unused volumes, not just anonymous ones |
|
||||||
| `-f`, `--force` | | | Do not prompt for confirmation |
|
| [`--filter`](#filter) | `filter` | | Provide filter values (e.g. `label=<label>`) |
|
||||||
|
| `-f`, `--force` | | | Do not prompt for confirmation |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## 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
|
## Examples
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker volume prune
|
$ 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
|
Are you sure you want to continue? [y/N] y
|
||||||
Deleted Volumes:
|
Deleted Volumes:
|
||||||
07c7bdf3e34ab76d921894c2b834f073721fccfbbcba792aa7648e3a7a664c2e
|
07c7bdf3e34ab76d921894c2b834f073721fccfbbcba792aa7648e3a7a664c2e
|
||||||
|
@ -31,6 +33,10 @@ my-named-vol
|
||||||
Total reclaimed space: 36 B
|
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)
|
### <a name="filter"></a> Filtering (--filter)
|
||||||
|
|
||||||
The filtering flag (`--filter`) format is of "key=value". If there is more
|
The filtering flag (`--filter`) format is of "key=value". If there is more
|
||||||
|
|
Loading…
Reference in New Issue