github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/system/prune.go (about) 1 package system 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "sort" 8 "text/template" 9 10 "github.com/docker/cli/cli" 11 "github.com/docker/cli/cli/command" 12 "github.com/docker/cli/cli/command/builder" 13 "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" 18 "github.com/docker/cli/opts" 19 "github.com/docker/docker/api/types/versions" 20 "github.com/docker/docker/errdefs" 21 "github.com/docker/go-units" 22 "github.com/fvbommel/sortorder" 23 "github.com/pkg/errors" 24 "github.com/spf13/cobra" 25 ) 26 27 type pruneOptions struct { 28 force bool 29 all bool 30 pruneVolumes bool 31 pruneBuildCache bool 32 filter opts.FilterOpt 33 } 34 35 // newPruneCommand creates a new cobra.Command for `docker prune` 36 func newPruneCommand(dockerCli command.Cli) *cobra.Command { 37 options := pruneOptions{filter: opts.NewFilterOpt()} 38 39 cmd := &cobra.Command{ 40 Use: "prune [OPTIONS]", 41 Short: "Remove unused data", 42 Args: cli.NoArgs, 43 RunE: func(cmd *cobra.Command, args []string) error { 44 options.pruneBuildCache = versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31") 45 return runPrune(cmd.Context(), dockerCli, options) 46 }, 47 Annotations: map[string]string{"version": "1.25"}, 48 ValidArgsFunction: completion.NoComplete, 49 } 50 51 flags := cmd.Flags() 52 flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation") 53 flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images not just dangling ones") 54 flags.BoolVar(&options.pruneVolumes, "volumes", false, "Prune anonymous volumes") 55 flags.Var(&options.filter, "filter", `Provide filter values (e.g. "label=<key>=<value>")`) 56 // "filter" flag is available in 1.28 (docker 17.04) and up 57 flags.SetAnnotation("filter", "version", []string{"1.28"}) 58 59 return cmd 60 } 61 62 const confirmationTemplate = `WARNING! This will remove: 63 {{- range $_, $warning := .warnings }} 64 - {{ $warning }} 65 {{- end }} 66 {{if .filters}} 67 Items to be pruned will be filtered with: 68 {{- range $_, $filters := .filters }} 69 - {{ $filters }} 70 {{- end }} 71 {{end}} 72 Are you sure you want to continue?` 73 74 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 fmt.Errorf(`ERROR: The "until" filter is not supported with "--volumes"`) 78 } 79 if !options.force { 80 r, err := command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), confirmationMessage(dockerCli, options)) 81 if err != nil { 82 return err 83 } 84 if !r { 85 return errdefs.Cancelled(errors.New("system prune has been cancelled")) 86 } 87 } 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 100 var spaceReclaimed uint64 101 for _, pruneFn := range pruneFuncs { 102 spc, output, err := pruneFn(ctx, dockerCli, options.all, options.filter) 103 if err != nil { 104 return err 105 } 106 spaceReclaimed += spc 107 if output != "" { 108 fmt.Fprintln(dockerCli.Out(), output) 109 } 110 } 111 112 fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) 113 114 return nil 115 } 116 117 // confirmationMessage constructs a confirmation message that depends on the cli options. 118 func confirmationMessage(dockerCli command.Cli, options pruneOptions) string { 119 t := template.Must(template.New("confirmation message").Parse(confirmationTemplate)) 120 121 warnings := []string{ 122 "all stopped containers", 123 "all networks not used by at least one container", 124 } 125 if options.pruneVolumes { 126 warnings = append(warnings, "all anonymous volumes not used by at least one container") 127 } 128 if options.all { 129 warnings = append(warnings, "all images without at least one container associated to them") 130 } else { 131 warnings = append(warnings, "all dangling images") 132 } 133 if options.pruneBuildCache { 134 if options.all { 135 warnings = append(warnings, "all build cache") 136 } else { 137 warnings = append(warnings, "unused build cache") 138 } 139 } 140 141 var filters []string 142 pruneFilters := command.PruneFilters(dockerCli, options.filter.Value()) 143 if pruneFilters.Len() > 0 { 144 // TODO remove fixed list of filters, and print all filters instead, 145 // because the list of filters that is supported by the engine may evolve over time. 146 for _, name := range []string{"label", "label!", "until"} { 147 for _, v := range pruneFilters.Get(name) { 148 filters = append(filters, name+"="+v) 149 } 150 } 151 sort.Slice(filters, func(i, j int) bool { 152 return sortorder.NaturalLess(filters[i], filters[j]) 153 }) 154 } 155 156 var buffer bytes.Buffer 157 t.Execute(&buffer, map[string][]string{"warnings": warnings, "filters": filters}) 158 return buffer.String() 159 }