@@ -3,33 +3,28 @@ package system
33import (
44 "bytes"
55 "context"
6+ "errors"
67 "fmt"
78 "sort"
89 "text/template"
910
11+ "github.com/containerd/errdefs"
1012 "github.com/docker/cli/cli"
1113 "github.com/docker/cli/cli/command"
12- "github.com/docker/cli/cli/command/builder"
1314 "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"
1816 "github.com/docker/cli/internal/prompt"
1917 "github.com/docker/cli/opts"
2018 "github.com/docker/go-units"
2119 "github.com/fvbommel/sortorder"
22- "github.com/moby/moby/api/types/versions"
23- "github.com/pkg/errors"
2420 "github.com/spf13/cobra"
2521)
2622
2723type 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
3328}
3429
3530// newPruneCommand creates a new cobra.Command for `docker prune`
@@ -41,7 +36,6 @@ func newPruneCommand(dockerCli command.Cli) *cobra.Command {
4136 Short : "Remove unused data" ,
4237 Args : cli .NoArgs ,
4338 RunE : func (cmd * cobra.Command , args []string ) error {
44- options .pruneBuildCache = versions .GreaterThanOrEqualTo (dockerCli .Client ().ClientVersion (), "1.31" )
4539 return runPrune (cmd .Context (), dockerCli , options )
4640 },
4741 Annotations : map [string ]string {"version" : "1.25" },
@@ -72,35 +66,44 @@ const confirmationTemplate = `WARNING! This will remove:
7266Are you sure you want to continue?`
7367
7468func 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
7876 }
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 )
8180 if err != nil {
8281 return err
8382 }
84- if ! r {
83+ if ! confirmed {
8584 return cancelledErr {errors .New ("system prune has been cancelled" )}
8685 }
8786 }
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- }
9987
10088 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 ) {
104107 return err
105108 }
106109 spaceReclaimed += spc
@@ -118,29 +121,43 @@ type cancelledErr struct{ error }
118121
119122func (cancelledErr ) Cancelled () {}
120123
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 )
142156 }
143157 }
158+ if len (errs ) > 0 {
159+ return "" , errors .Join (errs ... )
160+ }
144161
145162 var filters []string
146163 pruneFilters := command .PruneFilters (dockerCli , options .filter .Value ())
@@ -158,6 +175,7 @@ func confirmationMessage(dockerCli command.Cli, options pruneOptions) string {
158175 }
159176
160177 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
163181}
0 commit comments