Skip to content

Commit 7025247

Browse files
committed
Add label filter for docker system prune
This fix tries to address the issue raised in 29999 where it was not possible to mask these items (like important non-removable stuff) from `docker system prune`. This fix adds `label` and `label!` field for `--filter` in `system prune`, so that it is possible to selectively prune items like: ``` $ docker container prune --filter label=foo $ docker container prune --filter label!=bar ``` Additional unit tests and integration tests have been added. This fix fixes 29999. Signed-off-by: Yong Tang <[email protected]>
1 parent e4c608a commit 7025247

14 files changed

Lines changed: 308 additions & 16 deletions

File tree

api/server/router/volume/volume_routes.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,12 @@ func (v *volumeRouter) postVolumesPrune(ctx context.Context, w http.ResponseWrit
7272
return err
7373
}
7474

75-
pruneReport, err := v.backend.VolumesPrune(filters.Args{})
75+
pruneFilters, err := filters.FromParam(r.Form.Get("filters"))
76+
if err != nil {
77+
return err
78+
}
79+
80+
pruneReport, err := v.backend.VolumesPrune(pruneFilters)
7681
if err != nil {
7782
return err
7883
}

cli/command/container/prune.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const warning = `WARNING! This will remove all stopped containers.
4949
Are you sure you want to continue?`
5050

5151
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
52-
pruneFilters := opts.filter.Value()
52+
pruneFilters := command.PruneFilters(dockerCli, opts.filter.Value())
5353

5454
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
5555
return

cli/command/image/prune.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Are you sure you want to continue?`
5858
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
5959
pruneFilters := opts.filter.Value()
6060
pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all))
61+
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
6162

6263
warning := danglingWarning
6364
if opts.all {

cli/command/network/prune.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const warning = `WARNING! This will remove all networks not used by at least one
4848
Are you sure you want to continue?`
4949

5050
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (output string, err error) {
51-
pruneFilters := opts.filter.Value()
51+
pruneFilters := command.PruneFilters(dockerCli, opts.filter.Value())
5252

5353
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
5454
return

cli/command/prune/prune.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func RunContainerPrune(dockerCli *command.DockerCli, filter opts.FilterOpt) (uin
3737

3838
// RunVolumePrune executes a prune command for volumes
3939
func RunVolumePrune(dockerCli *command.DockerCli, filter opts.FilterOpt) (uint64, string, error) {
40-
return volume.RunPrune(dockerCli)
40+
return volume.RunPrune(dockerCli, filter)
4141
}
4242

4343
// RunImagePrune executes a prune command for images

cli/command/utils.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"runtime"
1010
"strings"
1111

12+
"github.com/docker/docker/api/types/filters"
1213
"github.com/docker/docker/pkg/system"
1314
)
1415

@@ -85,3 +86,34 @@ func PromptForConfirmation(ins *InStream, outs *OutStream, message string) bool
8586
answer, _, _ := reader.ReadLine()
8687
return strings.ToLower(string(answer)) == "y"
8788
}
89+
90+
// PruneFilters returns consolidated prune filters obtained from config.json and cli
91+
func PruneFilters(dockerCli Cli, pruneFilters filters.Args) filters.Args {
92+
if dockerCli.ConfigFile() == nil {
93+
return pruneFilters
94+
}
95+
for _, f := range dockerCli.ConfigFile().PruneFilters {
96+
parts := strings.SplitN(f, "=", 2)
97+
if len(parts) != 2 {
98+
continue
99+
}
100+
if parts[0] == "label" {
101+
// CLI label filter supersede config.json.
102+
// If CLI label filter conflict with config.json,
103+
// skip adding label! filter in config.json.
104+
if pruneFilters.Include("label!") && pruneFilters.ExactMatch("label!", parts[1]) {
105+
continue
106+
}
107+
} else if parts[0] == "label!" {
108+
// CLI label! filter supersede config.json.
109+
// If CLI label! filter conflict with config.json,
110+
// skip adding label filter in config.json.
111+
if pruneFilters.Include("label") && pruneFilters.ExactMatch("label", parts[1]) {
112+
continue
113+
}
114+
}
115+
pruneFilters.Add(parts[0], parts[1])
116+
}
117+
118+
return pruneFilters
119+
}

cli/command/volume/prune.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,22 @@ package volume
33
import (
44
"fmt"
55

6-
"github.com/docker/docker/api/types/filters"
76
"github.com/docker/docker/cli"
87
"github.com/docker/docker/cli/command"
8+
"github.com/docker/docker/opts"
99
units "github.com/docker/go-units"
1010
"github.com/spf13/cobra"
1111
"golang.org/x/net/context"
1212
)
1313

1414
type pruneOptions struct {
15-
force bool
15+
force bool
16+
filter opts.FilterOpt
1617
}
1718

1819
// NewPruneCommand returns a new cobra prune command for volumes
1920
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
20-
var opts pruneOptions
21+
opts := pruneOptions{filter: opts.NewFilterOpt()}
2122

2223
cmd := &cobra.Command{
2324
Use: "prune [OPTIONS]",
@@ -39,6 +40,7 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
3940

4041
flags := cmd.Flags()
4142
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
43+
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'label=<label>')")
4244

4345
return cmd
4446
}
@@ -47,11 +49,13 @@ const warning = `WARNING! This will remove all volumes not used by at least one
4749
Are you sure you want to continue?`
4850

4951
func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
52+
pruneFilters := command.PruneFilters(dockerCli, opts.filter.Value())
53+
5054
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
5155
return
5256
}
5357

54-
report, err := dockerCli.Client().VolumesPrune(context.Background(), filters.Args{})
58+
report, err := dockerCli.Client().VolumesPrune(context.Background(), pruneFilters)
5559
if err != nil {
5660
return
5761
}
@@ -69,6 +73,6 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64,
6973

7074
// RunPrune calls the Volume Prune API
7175
// This returns the amount of space reclaimed and a detailed output string
72-
func RunPrune(dockerCli *command.DockerCli) (uint64, string, error) {
73-
return runPrune(dockerCli, pruneOptions{force: true})
76+
func RunPrune(dockerCli *command.DockerCli, filter opts.FilterOpt) (uint64, string, error) {
77+
return runPrune(dockerCli, pruneOptions{force: true, filter: filter})
7478
}

cli/config/configfile/file.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type ConfigFile struct {
3939
TasksFormat string `json:"tasksFormat,omitempty"`
4040
SecretFormat string `json:"secretFormat,omitempty"`
4141
NodesFormat string `json:"nodesFormat,omitempty"`
42+
PruneFilters []string `json:"pruneFilters,omitempty"`
4243
}
4344

4445
// LegacyLoadFromReader reads the non-nested configuration data given and sets up the

client/container_prune_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ func TestContainersPrune(t *testing.T) {
4040
danglingUntilFilters.Add("dangling", "true")
4141
danglingUntilFilters.Add("until", "2016-12-15T14:00")
4242

43+
labelFilters := filters.NewArgs()
44+
labelFilters.Add("dangling", "true")
45+
labelFilters.Add("label", "label1=foo")
46+
labelFilters.Add("label", "label2!=bar")
47+
4348
listCases := []struct {
4449
filters filters.Args
4550
expectedQueryParams map[string]string
@@ -76,6 +81,14 @@ func TestContainersPrune(t *testing.T) {
7681
"filters": `{"dangling":{"false":true}}`,
7782
},
7883
},
84+
{
85+
filters: labelFilters,
86+
expectedQueryParams: map[string]string{
87+
"until": "",
88+
"filter": "",
89+
"filters": `{"dangling":{"true":true},"label":{"label1=foo":true,"label2!=bar":true}}`,
90+
},
91+
},
7992
}
8093
for _, listCase := range listCases {
8194
client := &Client{

client/image_prune_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ func TestImagesPrune(t *testing.T) {
3636
noDanglingFilters := filters.NewArgs()
3737
noDanglingFilters.Add("dangling", "false")
3838

39+
labelFilters := filters.NewArgs()
40+
labelFilters.Add("dangling", "true")
41+
labelFilters.Add("label", "label1=foo")
42+
labelFilters.Add("label", "label2!=bar")
43+
3944
listCases := []struct {
4045
filters filters.Args
4146
expectedQueryParams map[string]string
@@ -64,6 +69,14 @@ func TestImagesPrune(t *testing.T) {
6469
"filters": `{"dangling":{"false":true}}`,
6570
},
6671
},
72+
{
73+
filters: labelFilters,
74+
expectedQueryParams: map[string]string{
75+
"until": "",
76+
"filter": "",
77+
"filters": `{"dangling":{"true":true},"label":{"label1=foo":true,"label2!=bar":true}}`,
78+
},
79+
},
6780
}
6881
for _, listCase := range listCases {
6982
client := &Client{

0 commit comments

Comments
 (0)