@@ -3,33 +3,28 @@ package system
3
3
import (
4
4
"bytes"
5
5
"context"
6
+ "errors"
6
7
"fmt"
7
8
"sort"
8
9
"text/template"
9
10
11
+ "github.com/containerd/errdefs"
10
12
"github.com/docker/cli/cli"
11
13
"github.com/docker/cli/cli/command"
12
- "github.com/docker/cli/cli/command/builder"
13
14
"github.com/docker/cli/cli/command/completion"
14
- "github.com/docker/cli/cli/command/container"
15
- "github.com/docker/cli/cli/command/image"
16
- "github.com/docker/cli/cli/command/network"
17
- "github.com/docker/cli/cli/command/volume"
15
+ "github.com/docker/cli/cli/command/system/pruner"
18
16
"github.com/docker/cli/internal/prompt"
19
17
"github.com/docker/cli/opts"
20
18
"github.com/docker/go-units"
21
19
"github.com/fvbommel/sortorder"
22
- "github.com/moby/moby/api/types/versions"
23
- "github.com/pkg/errors"
24
20
"github.com/spf13/cobra"
25
21
)
26
22
27
23
type pruneOptions struct {
28
- force bool
29
- all bool
30
- pruneVolumes bool
31
- pruneBuildCache bool
32
- filter opts.FilterOpt
24
+ force bool
25
+ all bool
26
+ pruneVolumes bool
27
+ filter opts.FilterOpt
33
28
}
34
29
35
30
// newPruneCommand creates a new cobra.Command for `docker prune`
@@ -41,7 +36,6 @@ func newPruneCommand(dockerCli command.Cli) *cobra.Command {
41
36
Short : "Remove unused data" ,
42
37
Args : cli .NoArgs ,
43
38
RunE : func (cmd * cobra.Command , args []string ) error {
44
- options .pruneBuildCache = versions .GreaterThanOrEqualTo (dockerCli .Client ().ClientVersion (), "1.31" )
45
39
return runPrune (cmd .Context (), dockerCli , options )
46
40
},
47
41
Annotations : map [string ]string {"version" : "1.25" },
@@ -72,35 +66,44 @@ const confirmationTemplate = `WARNING! This will remove:
72
66
Are you sure you want to continue?`
73
67
74
68
func runPrune (ctx context.Context , dockerCli command.Cli , options pruneOptions ) error {
75
- // TODO version this once "until" filter is supported for volumes
76
- if options .pruneVolumes && options .filter .Value ().Contains ("until" ) {
77
- return errors .New (`ERROR: The "until" filter is not supported with "--volumes"` )
69
+ // prune requires either force, or a user to confirm after prompting.
70
+ confirmed := options .force
71
+
72
+ // Validate the given options for each pruner and construct a confirmation-message.
73
+ confirmationMessage , err := dryRun (ctx , dockerCli , options )
74
+ if err != nil {
75
+ return err
78
76
}
79
- if ! options .force {
80
- r , err := prompt .Confirm (ctx , dockerCli .In (), dockerCli .Out (), confirmationMessage (dockerCli , options ))
77
+ if ! confirmed {
78
+ var err error
79
+ confirmed , err = prompt .Confirm (ctx , dockerCli .In (), dockerCli .Out (), confirmationMessage )
81
80
if err != nil {
82
81
return err
83
82
}
84
- if ! r {
83
+ if ! confirmed {
85
84
return cancelledErr {errors .New ("system prune has been cancelled" )}
86
85
}
87
86
}
88
- pruneFuncs := []func (ctx context.Context , dockerCli command.Cli , all bool , filter opts.FilterOpt ) (uint64 , string , error ){
89
- container .RunPrune ,
90
- network .RunPrune ,
91
- }
92
- if options .pruneVolumes {
93
- pruneFuncs = append (pruneFuncs , volume .RunPrune )
94
- }
95
- pruneFuncs = append (pruneFuncs , image .RunPrune )
96
- if options .pruneBuildCache {
97
- pruneFuncs = append (pruneFuncs , builder .CachePrune )
98
- }
99
87
100
88
var spaceReclaimed uint64
101
- for _ , pruneFn := range pruneFuncs {
102
- spc , output , err := pruneFn (ctx , dockerCli , options .all , options .filter )
103
- if err != nil {
89
+ for contentType , pruneFn := range pruner .List () {
90
+ switch contentType {
91
+ case pruner .TypeVolume :
92
+ if ! options .pruneVolumes {
93
+ continue
94
+ }
95
+ case pruner .TypeContainer , pruner .TypeNetwork , pruner .TypeImage , pruner .TypeBuildCache :
96
+ // no special handling; keeping the "exhaustive" linter happy.
97
+ default :
98
+ // other pruners; no special handling; keeping the "exhaustive" linter happy.
99
+ }
100
+
101
+ spc , output , err := pruneFn (ctx , dockerCli , pruner.PruneOptions {
102
+ Confirmed : confirmed ,
103
+ All : options .all ,
104
+ Filter : options .filter ,
105
+ })
106
+ if err != nil && ! errdefs .IsNotImplemented (err ) {
104
107
return err
105
108
}
106
109
spaceReclaimed += spc
@@ -118,29 +121,43 @@ type cancelledErr struct{ error }
118
121
119
122
func (cancelledErr ) Cancelled () {}
120
123
121
- // confirmationMessage constructs a confirmation message that depends on the cli options.
122
- func confirmationMessage (dockerCli command.Cli , options pruneOptions ) string {
123
- t := template .Must (template .New ("confirmation message" ).Parse (confirmationTemplate ))
124
-
125
- warnings := []string {
126
- "all stopped containers" ,
127
- "all networks not used by at least one container" ,
128
- }
129
- if options .pruneVolumes {
130
- warnings = append (warnings , "all anonymous volumes not used by at least one container" )
131
- }
132
- if options .all {
133
- warnings = append (warnings , "all images without at least one container associated to them" )
134
- } else {
135
- warnings = append (warnings , "all dangling images" )
136
- }
137
- if options .pruneBuildCache {
138
- if options .all {
139
- warnings = append (warnings , "all build cache" )
140
- } else {
141
- warnings = append (warnings , "unused build cache" )
124
+ // dryRun validates the given options for each prune-function and constructs
125
+ // a confirmation message that depends on the cli options.
126
+ func dryRun (ctx context.Context , dockerCli command.Cli , options pruneOptions ) (string , error ) {
127
+ var (
128
+ errs []error
129
+ warnings []string
130
+ )
131
+ for contentType , pruneFn := range pruner .List () {
132
+ switch contentType {
133
+ case pruner .TypeVolume :
134
+ if ! options .pruneVolumes {
135
+ continue
136
+ }
137
+ case pruner .TypeContainer , pruner .TypeNetwork , pruner .TypeImage , pruner .TypeBuildCache :
138
+ // no special handling; keeping the "exhaustive" linter happy.
139
+ default :
140
+ // other pruners; no special handling; keeping the "exhaustive" linter happy.
141
+ }
142
+ // Always run with "[pruner.PruneOptions.Confirmed] = false"
143
+ // to perform validation of the given options and produce
144
+ // a confirmation message for the pruner.
145
+ _ , confirmMsg , err := pruneFn (ctx , dockerCli , pruner.PruneOptions {
146
+ All : options .all ,
147
+ Filter : options .filter ,
148
+ })
149
+ // A "canceled" error is expected in dry-run mode; any other error
150
+ // must be returned as a "fatal" error.
151
+ if err != nil && ! errdefs .IsCanceled (err ) && ! errdefs .IsNotImplemented (err ) {
152
+ errs = append (errs , err )
153
+ }
154
+ if confirmMsg != "" {
155
+ warnings = append (warnings , confirmMsg )
142
156
}
143
157
}
158
+ if len (errs ) > 0 {
159
+ return "" , errors .Join (errs ... )
160
+ }
144
161
145
162
var filters []string
146
163
pruneFilters := command .PruneFilters (dockerCli , options .filter .Value ())
@@ -158,6 +175,7 @@ func confirmationMessage(dockerCli command.Cli, options pruneOptions) string {
158
175
}
159
176
160
177
var buffer bytes.Buffer
161
- t .Execute (& buffer , map [string ][]string {"warnings" : warnings , "filters" : filters })
162
- return buffer .String ()
178
+ t := template .Must (template .New ("confirmation message" ).Parse (confirmationTemplate ))
179
+ _ = t .Execute (& buffer , map [string ][]string {"warnings" : warnings , "filters" : filters })
180
+ return buffer .String (), nil
163
181
}
0 commit comments