Merge pull request #6236 from thaJeztah/system_prune_register
system prune: refactor to use "register" functions
This commit is contained in:
@ -9,13 +9,22 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/system/pruner"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/moby/moby/api/types/build"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register the prune command to run as part of "docker system prune"
|
||||
if err := pruner.Register(pruner.TypeBuildCache, pruneFn); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type pruneOptions struct {
|
||||
force bool
|
||||
all bool
|
||||
@ -104,7 +113,30 @@ type cancelledErr struct{ error }
|
||||
|
||||
func (cancelledErr) Cancelled() {}
|
||||
|
||||
// CachePrune executes a prune command for build cache
|
||||
func CachePrune(ctx context.Context, dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
|
||||
return runPrune(ctx, dockerCli, pruneOptions{force: true, all: all, filter: filter})
|
||||
type errNotImplemented struct{ error }
|
||||
|
||||
func (errNotImplemented) NotImplemented() {}
|
||||
|
||||
// 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 ver := dockerCLI.Client().ClientVersion(); ver != "" && versions.LessThan(ver, "1.31") {
|
||||
// Not supported on older daemons.
|
||||
return 0, "", errNotImplemented{errors.New("builder prune requires API version 1.31 or greater")}
|
||||
}
|
||||
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,
|
||||
filter: options.Filter,
|
||||
})
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/system/pruner"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/go-units"
|
||||
@ -14,6 +15,13 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register the prune command to run as part of "docker system prune"
|
||||
if err := pruner.Register(pruner.TypeContainer, pruneFn); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type pruneOptions struct {
|
||||
force bool
|
||||
filter opts.FilterOpt
|
||||
@ -85,8 +93,16 @@ type cancelledErr struct{ error }
|
||||
|
||||
func (cancelledErr) Cancelled() {}
|
||||
|
||||
// RunPrune calls the Container Prune API
|
||||
// This returns the amount of space reclaimed and a detailed output string
|
||||
func RunPrune(ctx context.Context, dockerCli command.Cli, _ bool, filter opts.FilterOpt) (uint64, string, error) {
|
||||
return runPrune(ctx, dockerCli, pruneOptions{force: true, filter: filter})
|
||||
// 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,
|
||||
})
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/system/pruner"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/go-units"
|
||||
@ -16,6 +17,13 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register the prune command to run as part of "docker system prune"
|
||||
if err := pruner.Register(pruner.TypeImage, pruneFn); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type pruneOptions struct {
|
||||
force bool
|
||||
all bool
|
||||
@ -109,8 +117,22 @@ type cancelledErr struct{ error }
|
||||
|
||||
func (cancelledErr) Cancelled() {}
|
||||
|
||||
// RunPrune calls the Image Prune API
|
||||
// This returns the amount of space reclaimed and a detailed output string
|
||||
func RunPrune(ctx context.Context, dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
|
||||
return runPrune(ctx, dockerCli, pruneOptions{force: true, all: all, filter: filter})
|
||||
// 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,
|
||||
filter: options.Filter,
|
||||
})
|
||||
}
|
||||
|
||||
@ -7,11 +7,19 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/system/pruner"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register the prune command to run as part of "docker system prune"
|
||||
if err := pruner.Register(pruner.TypeNetwork, pruneFn); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type pruneOptions struct {
|
||||
force bool
|
||||
filter opts.FilterOpt
|
||||
@ -80,9 +88,17 @@ type cancelledErr struct{ error }
|
||||
|
||||
func (cancelledErr) Cancelled() {}
|
||||
|
||||
// RunPrune calls the Network Prune API
|
||||
// This returns the amount of space reclaimed and a detailed output string
|
||||
func RunPrune(ctx context.Context, dockerCli command.Cli, _ bool, filter opts.FilterOpt) (uint64, string, error) {
|
||||
output, err := runPrune(ctx, dockerCli, pruneOptions{force: true, filter: filter})
|
||||
// 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,
|
||||
})
|
||||
return 0, output, err
|
||||
}
|
||||
|
||||
@ -3,33 +3,28 @@ 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/builder"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/container"
|
||||
"github.com/docker/cli/cli/command/image"
|
||||
"github.com/docker/cli/cli/command/network"
|
||||
"github.com/docker/cli/cli/command/volume"
|
||||
"github.com/docker/cli/cli/command/system/pruner"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/moby/moby/api/types/versions"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type pruneOptions struct {
|
||||
force bool
|
||||
all bool
|
||||
pruneVolumes bool
|
||||
pruneBuildCache bool
|
||||
filter opts.FilterOpt
|
||||
force bool
|
||||
all bool
|
||||
pruneVolumes bool
|
||||
filter opts.FilterOpt
|
||||
}
|
||||
|
||||
// newPruneCommand creates a new cobra.Command for `docker prune`
|
||||
@ -41,7 +36,6 @@ func newPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||
Short: "Remove unused data",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.pruneBuildCache = versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31")
|
||||
return runPrune(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
@ -72,35 +66,44 @@ 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")}
|
||||
}
|
||||
}
|
||||
pruneFuncs := []func(ctx context.Context, dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error){
|
||||
container.RunPrune,
|
||||
network.RunPrune,
|
||||
}
|
||||
if options.pruneVolumes {
|
||||
pruneFuncs = append(pruneFuncs, volume.RunPrune)
|
||||
}
|
||||
pruneFuncs = append(pruneFuncs, image.RunPrune)
|
||||
if options.pruneBuildCache {
|
||||
pruneFuncs = append(pruneFuncs, builder.CachePrune)
|
||||
}
|
||||
|
||||
var spaceReclaimed uint64
|
||||
for _, pruneFn := range pruneFuncs {
|
||||
spc, output, err := pruneFn(ctx, dockerCli, options.all, options.filter)
|
||||
if err != nil {
|
||||
for contentType, pruneFn := range pruner.List() {
|
||||
switch contentType {
|
||||
case pruner.TypeVolume:
|
||||
if !options.pruneVolumes {
|
||||
continue
|
||||
}
|
||||
case pruner.TypeContainer, pruner.TypeNetwork, pruner.TypeImage, pruner.TypeBuildCache:
|
||||
// no special handling; keeping the "exhaustive" linter happy.
|
||||
default:
|
||||
// other pruners; no special handling; keeping the "exhaustive" linter happy.
|
||||
}
|
||||
|
||||
spc, output, err := pruneFn(ctx, dockerCli, pruner.PruneOptions{
|
||||
Confirmed: confirmed,
|
||||
All: options.all,
|
||||
Filter: options.filter,
|
||||
})
|
||||
if err != nil && !errdefs.IsNotImplemented(err) {
|
||||
return err
|
||||
}
|
||||
spaceReclaimed += spc
|
||||
@ -118,28 +121,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.TypeContainer, pruner.TypeNetwork, pruner.TypeImage, pruner.TypeBuildCache:
|
||||
// no special handling; keeping the "exhaustive" linter happy.
|
||||
default:
|
||||
// other pruners; no special handling; keeping the "exhaustive" linter happy.
|
||||
}
|
||||
// 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) && !errdefs.IsNotImplemented(err) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if confirmMsg != "" {
|
||||
warnings = append(warnings, confirmMsg)
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return "", errors.Join(errs...)
|
||||
}
|
||||
|
||||
var filters []string
|
||||
@ -158,6 +175,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
|
||||
}
|
||||
|
||||
@ -13,6 +13,13 @@ import (
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
|
||||
// Make sure pruners are registered for tests (they're included automatically when building).
|
||||
_ "github.com/docker/cli/cli/command/builder"
|
||||
_ "github.com/docker/cli/cli/command/container"
|
||||
_ "github.com/docker/cli/cli/command/image"
|
||||
_ "github.com/docker/cli/cli/command/network"
|
||||
_ "github.com/docker/cli/cli/command/volume"
|
||||
)
|
||||
|
||||
func TestPrunePromptPre131DoesNotIncludeBuildCache(t *testing.T) {
|
||||
|
||||
140
cli/command/system/pruner/pruner.go
Normal file
140
cli/command/system/pruner/pruner.go
Normal file
@ -0,0 +1,140 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.23
|
||||
|
||||
// Package pruner registers "prune" functions to be included as part of
|
||||
// "docker system prune".
|
||||
package pruner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/opts"
|
||||
)
|
||||
|
||||
// ContentType is an identifier for content that can be pruned.
|
||||
type ContentType string
|
||||
|
||||
// Pre-defined content-types to prune. Additional types can be registered,
|
||||
// and will be pruned after the list of pre-defined types.
|
||||
const (
|
||||
TypeContainer ContentType = "container"
|
||||
TypeNetwork ContentType = "network"
|
||||
TypeImage ContentType = "image"
|
||||
TypeVolume ContentType = "volume"
|
||||
TypeBuildCache ContentType = "buildcache"
|
||||
)
|
||||
|
||||
// pruneOrder is the order in which ContentType must be pruned. The order
|
||||
// in which pruning happens is important to make sure that resources are
|
||||
// released before pruning (e.g., a "container" can use a "network" and
|
||||
// "volume", so containers must be pruned before networks and volumes).
|
||||
var pruneOrder = []ContentType{
|
||||
TypeContainer,
|
||||
TypeNetwork,
|
||||
TypeVolume,
|
||||
TypeImage,
|
||||
TypeBuildCache,
|
||||
}
|
||||
|
||||
// PruneFunc is the signature for prune-functions. The action performed
|
||||
// depends on the [PruneOptions.Confirmed] field.
|
||||
//
|
||||
// - 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 or a [ErrNotImplemented] if the prune
|
||||
// function is not implemented for the daemon's API version. Any
|
||||
// other error is considered a validation error for 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
|
||||
// [ErrNotImplemented]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/errdefs#ErrNotImplemented
|
||||
type PruneFunc func(ctx context.Context, dockerCLI command.Cli, pruneOpts PruneOptions) (spaceReclaimed uint64, details string, _ error)
|
||||
|
||||
type PruneOptions struct {
|
||||
// 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].
|
||||
// It is considered immutable after startup.
|
||||
var registered map[ContentType]PruneFunc
|
||||
|
||||
// Register registers a [PruneFunc] under the given name to be included in
|
||||
// "docker system prune". It is designed to be called in an init function
|
||||
// and is not safe for concurrent use.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// func init() {
|
||||
// // Register the prune command to run as part of "docker system prune".
|
||||
// if err := prune.Register(prune.TypeImage, prunerFn); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// }
|
||||
func Register(name ContentType, pruneFunc PruneFunc) error {
|
||||
if name == "" {
|
||||
return errors.New("error registering pruner: invalid prune type: cannot be empty")
|
||||
}
|
||||
if pruneFunc == nil {
|
||||
return errors.New("error registering pruner: prune function is nil for " + string(name))
|
||||
}
|
||||
if registered == nil {
|
||||
registered = make(map[ContentType]PruneFunc)
|
||||
}
|
||||
if _, exists := registered[name]; exists {
|
||||
return fmt.Errorf("error registering pruner: content-type %s is already registered", name)
|
||||
}
|
||||
registered[name] = pruneFunc
|
||||
return nil
|
||||
}
|
||||
|
||||
// List iterates over all registered pruners, starting with known pruners
|
||||
// in their predefined order, followed by any others (sorted alphabetically).
|
||||
func List() iter.Seq2[ContentType, PruneFunc] {
|
||||
all := maps.Clone(registered)
|
||||
ordered := make([]ContentType, 0, len(all))
|
||||
for _, ct := range pruneOrder {
|
||||
if _, ok := all[ct]; ok {
|
||||
ordered = append(ordered, ct)
|
||||
delete(all, ct)
|
||||
}
|
||||
}
|
||||
// append any remaining content-types (if any) that may be registered.
|
||||
if len(all) > 0 {
|
||||
ordered = append(ordered, slices.Sorted(maps.Keys(all))...)
|
||||
}
|
||||
|
||||
return func(yield func(ContentType, PruneFunc) bool) {
|
||||
for _, ct := range ordered {
|
||||
if fn := registered[ct]; fn != nil {
|
||||
if !yield(ct, fn) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/system/pruner"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/go-units"
|
||||
@ -15,6 +16,13 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register the prune command to run as part of "docker system prune"
|
||||
if err := pruner.Register(pruner.TypeVolume, pruneFn); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type pruneOptions struct {
|
||||
all bool
|
||||
force bool
|
||||
@ -110,8 +118,22 @@ type cancelledErr struct{ error }
|
||||
|
||||
func (cancelledErr) Cancelled() {}
|
||||
|
||||
// RunPrune calls the Volume Prune API
|
||||
// This returns the amount of space reclaimed and a detailed output string
|
||||
func RunPrune(ctx context.Context, dockerCli command.Cli, _ bool, filter opts.FilterOpt) (uint64, string, error) {
|
||||
return runPrune(ctx, dockerCli, pruneOptions{force: true, filter: filter})
|
||||
// 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