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  }