mirror of
https://github.com/docker/cli.git
synced 2026-01-13 18:22:35 +03:00
system prune: delegate confirmation message and validation
This adds a "dry-run" / "pre-check" option for prune-functions, which delegates constructing the confirmation message (what is about to be pruned) and validation of the given options to the prune-functions. This helps separating concerns, and doesn't enforce knowledge about what's supported by each content-type onto the system prune command. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@@ -122,6 +122,16 @@ func CachePrune(ctx context.Context, dockerCli command.Cli, all bool, filter opt
|
||||
// pruneFn prunes the build cache for use in "docker system prune" and
|
||||
// returns the amount of space reclaimed and a detailed output string.
|
||||
func pruneFn(ctx context.Context, dockerCLI command.Cli, options pruner.PruneOptions) (uint64, string, error) {
|
||||
if !options.Confirmed {
|
||||
// Dry-run: perform validation and produce confirmation before pruning.
|
||||
var confirmMsg string
|
||||
if options.All {
|
||||
confirmMsg = "all build cache"
|
||||
} else {
|
||||
confirmMsg = "unused build cache"
|
||||
}
|
||||
return 0, confirmMsg, cancelledErr{errors.New("builder prune has been cancelled")}
|
||||
}
|
||||
return runPrune(ctx, dockerCLI, pruneOptions{
|
||||
force: true,
|
||||
all: options.All,
|
||||
|
||||
@@ -104,6 +104,11 @@ func RunPrune(ctx context.Context, dockerCli command.Cli, _ bool, filter opts.Fi
|
||||
// pruneFn calls the Container Prune API for use in "docker system prune",
|
||||
// and returns the amount of space reclaimed and a detailed output string.
|
||||
func pruneFn(ctx context.Context, dockerCLI command.Cli, options pruner.PruneOptions) (uint64, string, error) {
|
||||
if !options.Confirmed {
|
||||
// Dry-run: perform validation and produce confirmation before pruning.
|
||||
confirmMsg := "all stopped containers"
|
||||
return 0, confirmMsg, cancelledErr{errors.New("containers prune has been cancelled")}
|
||||
}
|
||||
return runPrune(ctx, dockerCLI, pruneOptions{
|
||||
force: true,
|
||||
filter: options.Filter,
|
||||
|
||||
@@ -125,9 +125,19 @@ func RunPrune(ctx context.Context, dockerCli command.Cli, all bool, filter opts.
|
||||
return runPrune(ctx, dockerCli, pruneOptions{force: true, all: all, filter: filter})
|
||||
}
|
||||
|
||||
// pruneFn calls the Container Prune API for use in "docker system prune",
|
||||
// pruneFn calls the Image Prune API for use in "docker system prune",
|
||||
// and returns the amount of space reclaimed and a detailed output string.
|
||||
func pruneFn(ctx context.Context, dockerCLI command.Cli, options pruner.PruneOptions) (uint64, string, error) {
|
||||
if !options.Confirmed {
|
||||
// Dry-run: perform validation and produce confirmation before pruning.
|
||||
var confirmMsg string
|
||||
if options.All {
|
||||
confirmMsg = "all images without at least one container associated to them"
|
||||
} else {
|
||||
confirmMsg = "all dangling images"
|
||||
}
|
||||
return 0, confirmMsg, cancelledErr{errors.New("image prune has been cancelled")}
|
||||
}
|
||||
return runPrune(ctx, dockerCLI, pruneOptions{
|
||||
force: true,
|
||||
all: options.All,
|
||||
|
||||
@@ -100,6 +100,11 @@ func RunPrune(ctx context.Context, dockerCli command.Cli, _ bool, filter opts.Fi
|
||||
// pruneFn calls the Network Prune API for use in "docker system prune"
|
||||
// and returns the amount of space reclaimed and a detailed output string.
|
||||
func pruneFn(ctx context.Context, dockerCLI command.Cli, options pruner.PruneOptions) (uint64, string, error) {
|
||||
if !options.Confirmed {
|
||||
// Dry-run: perform validation and produce confirmation before pruning.
|
||||
confirmMsg := "all networks not used by at least one container"
|
||||
return 0, confirmMsg, cancelledErr{errors.New("network prune has been cancelled")}
|
||||
}
|
||||
output, err := runPrune(ctx, dockerCLI, pruneOptions{
|
||||
force: true,
|
||||
filter: options.Filter,
|
||||
|
||||
@@ -3,10 +3,12 @@ package system
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"text/template"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
@@ -16,7 +18,6 @@ import (
|
||||
"github.com/docker/go-units"
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -68,16 +69,21 @@ const confirmationTemplate = `WARNING! This will remove:
|
||||
Are you sure you want to continue?`
|
||||
|
||||
func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) error {
|
||||
// TODO version this once "until" filter is supported for volumes
|
||||
if options.pruneVolumes && options.filter.Value().Contains("until") {
|
||||
return errors.New(`ERROR: The "until" filter is not supported with "--volumes"`)
|
||||
// prune requires either force, or a user to confirm after prompting.
|
||||
confirmed := options.force
|
||||
|
||||
// Validate the given options for each pruner and construct a confirmation-message.
|
||||
confirmationMessage, err := dryRun(ctx, dockerCli, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !options.force {
|
||||
r, err := prompt.Confirm(ctx, dockerCli.In(), dockerCli.Out(), confirmationMessage(dockerCli, options))
|
||||
if !confirmed {
|
||||
var err error
|
||||
confirmed, err = prompt.Confirm(ctx, dockerCli.In(), dockerCli.Out(), confirmationMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r {
|
||||
if !confirmed {
|
||||
return cancelledErr{errors.New("system prune has been cancelled")}
|
||||
}
|
||||
}
|
||||
@@ -100,8 +106,9 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
|
||||
}
|
||||
|
||||
spc, output, err := pruneFn(ctx, dockerCli, pruner.PruneOptions{
|
||||
All: options.all,
|
||||
Filter: options.filter,
|
||||
Confirmed: confirmed,
|
||||
All: options.all,
|
||||
Filter: options.filter,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -121,28 +128,42 @@ type cancelledErr struct{ error }
|
||||
|
||||
func (cancelledErr) Cancelled() {}
|
||||
|
||||
// confirmationMessage constructs a confirmation message that depends on the cli options.
|
||||
func confirmationMessage(dockerCli command.Cli, options pruneOptions) string {
|
||||
t := template.Must(template.New("confirmation message").Parse(confirmationTemplate))
|
||||
|
||||
warnings := []string{
|
||||
"all stopped containers",
|
||||
"all networks not used by at least one container",
|
||||
}
|
||||
if options.pruneVolumes {
|
||||
warnings = append(warnings, "all anonymous volumes not used by at least one container")
|
||||
}
|
||||
if options.all {
|
||||
warnings = append(warnings, "all images without at least one container associated to them")
|
||||
} else {
|
||||
warnings = append(warnings, "all dangling images")
|
||||
}
|
||||
if options.pruneBuildCache {
|
||||
if options.all {
|
||||
warnings = append(warnings, "all build cache")
|
||||
} else {
|
||||
warnings = append(warnings, "unused build cache")
|
||||
// dryRun validates the given options for each prune-function and constructs
|
||||
// a confirmation message that depends on the cli options.
|
||||
func dryRun(ctx context.Context, dockerCli command.Cli, options pruneOptions) (string, error) {
|
||||
var (
|
||||
errs []error
|
||||
warnings []string
|
||||
)
|
||||
for contentType, pruneFn := range pruner.List() {
|
||||
switch contentType {
|
||||
case pruner.TypeVolume:
|
||||
if !options.pruneVolumes {
|
||||
continue
|
||||
}
|
||||
case pruner.TypeBuildCache:
|
||||
if !options.pruneBuildCache {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Always run with "[pruner.PruneOptions.Confirmed] = false"
|
||||
// to perform validation of the given options and produce
|
||||
// a confirmation message for the pruner.
|
||||
_, confirmMsg, err := pruneFn(ctx, dockerCli, pruner.PruneOptions{
|
||||
All: options.all,
|
||||
Filter: options.filter,
|
||||
})
|
||||
// A "canceled" error is expected in dry-run mode; any other error
|
||||
// must be returned as a "fatal" error.
|
||||
if err != nil && !errdefs.IsCanceled(err) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if confirmMsg != "" {
|
||||
warnings = append(warnings, confirmMsg)
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return "", errors.Join(errs...)
|
||||
}
|
||||
|
||||
var filters []string
|
||||
@@ -161,6 +182,7 @@ func confirmationMessage(dockerCli command.Cli, options pruneOptions) string {
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
t.Execute(&buffer, map[string][]string{"warnings": warnings, "filters": filters})
|
||||
return buffer.String()
|
||||
t := template.Must(template.New("confirmation message").Parse(confirmationTemplate))
|
||||
_ = t.Execute(&buffer, map[string][]string{"warnings": warnings, "filters": filters})
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
@@ -42,16 +42,39 @@ var pruneOrder = []ContentType{
|
||||
TypeBuildCache,
|
||||
}
|
||||
|
||||
// PruneFunc is the signature for prune-functions. It returns details about
|
||||
// the content pruned;
|
||||
// PruneFunc is the signature for prune-functions. The action performed
|
||||
// depends on the [PruneOptions.Confirmed] field.
|
||||
//
|
||||
// - spaceReclaimed is the amount of data removed (in bytes).
|
||||
// - details is arbitrary information about the content pruned.
|
||||
// - If [PruneOptions.Confirmed] is "false", the PruneFunc must be run
|
||||
// in "dry-run" mode and return a short description of what content
|
||||
// will be pruned (for example, "all stopped containers") instead of
|
||||
// executing the prune. This summary is presented to the user as a
|
||||
// confirmation message. It may return a [ErrCancelled] to indicate
|
||||
// the operation was canceled. Any other error is considered a
|
||||
// validation error of the given options (such as a filter that
|
||||
// is not supported.
|
||||
// - If [PruneOptions.Confirmed] is "true", the PruneFunc must execute
|
||||
// the prune with the given options.
|
||||
//
|
||||
// After a successful prune the PruneFunc must return details about the
|
||||
// content pruned;
|
||||
//
|
||||
// - spaceReclaimed is the amount of data removed (in bytes), if any.
|
||||
// - details is arbitrary information about the content pruned to be
|
||||
// presented to the user.
|
||||
//
|
||||
// [ErrCancelled]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/errdefs#ErrCancelled
|
||||
type PruneFunc func(ctx context.Context, dockerCLI command.Cli, pruneOpts PruneOptions) (spaceReclaimed uint64, details string, _ error)
|
||||
|
||||
type PruneOptions struct {
|
||||
All bool
|
||||
Filter opts.FilterOpt
|
||||
// Confirmed indicates whether pruning was confirmed (or "forced")
|
||||
// by the user. If not set, the PruneFunc must be run in "dry-run"
|
||||
// mode and return a short description of what content will be pruned
|
||||
// (for example, "all stopped containers") instead of executing the
|
||||
// prune. This summary is presented to the user as a confirmation message.
|
||||
Confirmed bool
|
||||
All bool // Remove all unused content not just dangling (exact meaning differs per content-type).
|
||||
Filter opts.FilterOpt
|
||||
}
|
||||
|
||||
// registered holds a map of PruneFunc functions registered through [Register].
|
||||
|
||||
@@ -129,6 +129,17 @@ func RunPrune(ctx context.Context, dockerCli command.Cli, _ bool, filter opts.Fi
|
||||
// pruneFn calls the Volume Prune API for use in "docker system prune",
|
||||
// and returns the amount of space reclaimed and a detailed output string.
|
||||
func pruneFn(ctx context.Context, dockerCli command.Cli, options pruner.PruneOptions) (uint64, string, error) {
|
||||
// TODO version this once "until" filter is supported for volumes
|
||||
// Ideally, this check wasn't done on the CLI because the list of
|
||||
// filters that is supported by the daemon may evolve over time.
|
||||
if options.Filter.Value().Contains("until") {
|
||||
return 0, "", errors.New(`ERROR: The "until" filter is not supported with "--volumes"`)
|
||||
}
|
||||
if !options.Confirmed {
|
||||
// Dry-run: perform validation and produce confirmation before pruning.
|
||||
confirmMsg := "all anonymous volumes not used by at least one container"
|
||||
return 0, confirmMsg, cancelledErr{errors.New("volume prune has been cancelled")}
|
||||
}
|
||||
return runPrune(ctx, dockerCli, pruneOptions{
|
||||
force: true,
|
||||
filter: options.Filter,
|
||||
|
||||
Reference in New Issue
Block a user