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  }