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