system prune: delegate version check

Move the version-check for pruners to the pruner, which can
return a [ErrNotImplemented] error to indicate they won't
be run with the API version that's used.

This helps separating concerns, and doesn't enforce knowledge
about what's supported by each content-type onto the system
prune command.

[ErrNotImplemented]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/errdefs#ErrNotImplemented

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-08-03 22:59:23 +02:00
parent a888c4091c
commit bf8cb43025
3 changed files with 25 additions and 21 deletions

View File

@ -14,6 +14,7 @@ import (
"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"
)
@ -112,6 +113,10 @@ type cancelledErr struct{ error }
func (cancelledErr) Cancelled() {}
type errNotImplemented struct{ error }
func (errNotImplemented) NotImplemented() {}
// CachePrune executes a prune command for build cache
//
// Deprecated: this function was only used internally and will be removed in the next release.
@ -122,6 +127,10 @@ 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 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

View File

@ -17,16 +17,14 @@ import (
"github.com/docker/cli/opts"
"github.com/docker/go-units"
"github.com/fvbommel/sortorder"
"github.com/moby/moby/api/types/versions"
"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`
@ -38,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"},
@ -95,11 +92,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
if !options.pruneVolumes {
continue
}
case pruner.TypeBuildCache:
if !options.pruneBuildCache {
continue
}
case pruner.TypeContainer, pruner.TypeNetwork, pruner.TypeImage:
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.
@ -110,7 +103,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
All: options.all,
Filter: options.filter,
})
if err != nil {
if err != nil && !errdefs.IsNotImplemented(err) {
return err
}
spaceReclaimed += spc
@ -141,10 +134,10 @@ func dryRun(ctx context.Context, dockerCli command.Cli, options pruneOptions) (s
if !options.pruneVolumes {
continue
}
case pruner.TypeBuildCache:
if !options.pruneBuildCache {
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
@ -155,7 +148,7 @@ func dryRun(ctx context.Context, dockerCli command.Cli, options pruneOptions) (s
})
// 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) {
if err != nil && !errdefs.IsCanceled(err) && !errdefs.IsNotImplemented(err) {
errs = append(errs, err)
}
if confirmMsg != "" {

View File

@ -50,9 +50,10 @@ var pruneOrder = []ContentType{
// 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.
// 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.
//
@ -64,6 +65,7 @@ var pruneOrder = []ContentType{
// 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 {