From d36f16e22490a0ff1585b7cea41a6e140ca2de4c Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 10 Oct 2025 18:54:19 +0200 Subject: [PATCH 1/5] remove API-version compatibility for API < v1.44 Support for API versions < v1.44 was removed in the client in [moby@96b29f5] and [moby@7652f38], so we can remove fallback-code from the CLI as well, as it won't be able to use those versions. [moby@96b29f5]: https://github.com/moby/moby/commit/96b29f5a1f7fc5e5d8b2b4dbd130e215bbb92ae9 [moby@7652f38]: https://github.com/moby/moby/commit/7652f38c289909bc61b1113c0570b9de345bbed9 Signed-off-by: Sebastiaan van Stijn --- cli/command/builder/prune.go | 9 ----- cli/command/cli_test.go | 4 +- cli/command/container/create.go | 7 +--- cli/command/container/run_test.go | 12 +++--- cli/command/formatter/buildcache.go | 2 - cli/command/service/create.go | 7 +--- cli/command/service/list_test.go | 59 ++-------------------------- cli/command/service/rollback.go | 3 +- cli/command/service/rollback_test.go | 3 +- cli/command/service/scale.go | 3 +- cli/command/service/update.go | 34 +++------------- cli/command/stack/client_test.go | 6 +-- cli/command/stack/deploy.go | 8 ---- cli/command/stack/remove.go | 19 +++------ cli/command/stack/remove_test.go | 43 ++++---------------- cli/command/system/prune_test.go | 18 +-------- cli/command/volume/prune.go | 14 ++----- cli/compose/convert/service.go | 18 +-------- cli/compose/convert/service_test.go | 4 +- e2e/container/run_test.go | 1 - e2e/global/cli_test.go | 3 -- 21 files changed, 49 insertions(+), 228 deletions(-) diff --git a/cli/command/builder/prune.go b/cli/command/builder/prune.go index 2c5e0c8769fe..a7c88dcc42ac 100644 --- a/cli/command/builder/prune.go +++ b/cli/command/builder/prune.go @@ -12,7 +12,6 @@ import ( "github.com/docker/cli/internal/prompt" "github.com/docker/cli/opts" "github.com/docker/go-units" - "github.com/moby/moby/api/types/versions" "github.com/moby/moby/client" "github.com/spf13/cobra" ) @@ -112,17 +111,9 @@ type cancelledErr struct{ error } func (cancelledErr) Cancelled() {} -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 diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index 3fea15575768..adc087a213d9 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -164,7 +164,7 @@ func TestInitializeFromClient(t *testing.T) { { doc: "successful ping", pingFunc: func() (types.Ping, error) { - return types.Ping{Experimental: true, OSType: "linux", APIVersion: "v1.30"}, nil + return types.Ping{Experimental: true, OSType: "linux", APIVersion: "v1.44"}, nil }, expectedServer: ServerInfo{HasExperimental: true, OSType: "linux"}, negotiated: true, @@ -179,7 +179,7 @@ func TestInitializeFromClient(t *testing.T) { { doc: "failed ping, with API version", pingFunc: func() (types.Ping, error) { - return types.Ping{APIVersion: "v1.33"}, errors.New("failed") + return types.Ping{APIVersion: "v1.44"}, errors.New("failed") }, expectedServer: ServerInfo{HasExperimental: true}, negotiated: true, diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 3faacc382a03..55b09a22c1e2 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -25,7 +25,6 @@ import ( "github.com/docker/cli/internal/jsonstream" "github.com/docker/cli/opts" "github.com/moby/moby/api/types/mount" - "github.com/moby/moby/api/types/versions" "github.com/moby/moby/client" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" @@ -306,11 +305,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c } var platform *ocispec.Platform - // Engine API version 1.41 first introduced the option to specify platform on - // create. It will produce an error if you try to set a platform on older API - // versions, so check the API version here to maintain backwards - // compatibility for CLI users. - if options.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") { + if options.platform != "" { p, err := platforms.Parse(options.platform) if err != nil { return "", invalidParameter(fmt.Errorf("error parsing specified platform: %w", err)) diff --git a/cli/command/container/run_test.go b/cli/command/container/run_test.go index 925fc0d73f38..232d700a69d6 100644 --- a/cli/command/container/run_test.go +++ b/cli/command/container/run_test.go @@ -61,7 +61,7 @@ func TestRunLabel(t *testing.T) { ID: "id", }, nil }, - Version: "1.36", + Version: client.MaxAPIVersion, }) cmd := newRunCommand(fakeCLI) cmd.SetArgs([]string{"--detach=true", "--label", "foo", "busybox"}) @@ -103,8 +103,8 @@ func TestRunAttach(t *testing.T) { return responseChan, errChan }, // use new (non-legacy) wait API - // see: 38591f20d07795aaef45d400df89ca12f29c603b - Version: "1.30", + // see: https://github.com/docker/cli/commit/38591f20d07795aaef45d400df89ca12f29c603b + Version: client.MaxAPIVersion, }, func(fc *test.FakeCli) { fc.SetOut(streams.NewOut(tty)) fc.SetIn(streams.NewIn(tty)) @@ -180,8 +180,8 @@ func TestRunAttachTermination(t *testing.T) { return responseChan, errChan }, // use new (non-legacy) wait API - // see: 38591f20d07795aaef45d400df89ca12f29c603b - Version: "1.30", + // see: https://github.com/docker/cli/commit/38591f20d07795aaef45d400df89ca12f29c603b + Version: client.MaxAPIVersion, }, func(fc *test.FakeCli) { fc.SetOut(streams.NewOut(tty)) fc.SetIn(streams.NewIn(tty)) @@ -262,7 +262,7 @@ func TestRunPullTermination(t *testing.T) { attachCh <- struct{}{} return respReader, nil }, - Version: "1.30", + Version: client.MaxAPIVersion, }) cmd := newRunCommand(fakeCLI) diff --git a/cli/command/formatter/buildcache.go b/cli/command/formatter/buildcache.go index 68ad421ae0a4..5fa4541efa27 100644 --- a/cli/command/formatter/buildcache.go +++ b/cli/command/formatter/buildcache.go @@ -126,8 +126,6 @@ func (c *buildCacheContext) Parent() string { var parent string if len(c.v.Parents) > 0 { parent = strings.Join(c.v.Parents, ", ") - } else { - parent = c.v.Parent //nolint:staticcheck // Ignore SA1019: Field was deprecated in API v1.42, but kept for backward compatibility } if c.trunc { return TruncateID(parent) diff --git a/cli/command/service/create.go b/cli/command/service/create.go index d5830f5c9d66..c3c9565b19e2 100644 --- a/cli/command/service/create.go +++ b/cli/command/service/create.go @@ -9,7 +9,6 @@ import ( "github.com/docker/cli/cli/command/completion" cliopts "github.com/docker/cli/opts" "github.com/moby/moby/api/types/swarm" - "github.com/moby/moby/api/types/versions" "github.com/moby/moby/client" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -132,9 +131,7 @@ func runCreate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, } // query registry if flag disabling it was not set - if !opts.noResolveImage && versions.GreaterThanOrEqualTo(apiClient.ClientVersion(), "1.30") { - createOpts.QueryRegistry = true - } + createOpts.QueryRegistry = !opts.noResolveImage response, err := apiClient.ServiceCreate(ctx, service, createOpts) if err != nil { @@ -147,7 +144,7 @@ func runCreate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, _, _ = fmt.Fprintln(dockerCLI.Out(), response.ID) - if opts.detach || versions.LessThan(apiClient.ClientVersion(), "1.29") { + if opts.detach { return nil } diff --git a/cli/command/service/list_test.go b/cli/command/service/list_test.go index 479e9a9539d7..5bb2f6a71d0c 100644 --- a/cli/command/service/list_test.go +++ b/cli/command/service/list_test.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test/builders" "github.com/moby/moby/api/types/swarm" - "github.com/moby/moby/api/types/versions" "github.com/moby/moby/client" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" @@ -63,59 +62,12 @@ func TestServiceListServiceStatus(t *testing.T) { cluster: &cluster{}, // force an empty cluster expected: []listResponse{}, }, - { - // Services are running, but no active nodes were found. On API v1.40 - // and below, this will cause looking up the "running" tasks to fail, - // as well as looking up "desired" tasks for global services. - doc: "API v1.40 no active nodes", - opts: clusterOpts{ - apiVersion: "1.40", - activeNodes: 0, - runningTasks: 2, - desiredTasks: 4, - }, - expected: []listResponse{ - {ID: "replicated", Replicas: "0/4"}, - {ID: "global", Replicas: "0/0"}, - {ID: "none-id", Replicas: "0/0"}, - }, - }, - { - doc: "API v1.40 3 active nodes, 1 task running", - opts: clusterOpts{ - apiVersion: "1.40", - activeNodes: 3, - runningTasks: 1, - desiredTasks: 2, - }, - expected: []listResponse{ - {ID: "replicated", Replicas: "1/2"}, - {ID: "global", Replicas: "1/3"}, - {ID: "none-id", Replicas: "0/0"}, - }, - }, - { - doc: "API v1.40 3 active nodes, all tasks running", - opts: clusterOpts{ - apiVersion: "1.40", - activeNodes: 3, - runningTasks: 3, - desiredTasks: 3, - }, - expected: []listResponse{ - {ID: "replicated", Replicas: "3/3"}, - {ID: "global", Replicas: "3/3"}, - {ID: "none-id", Replicas: "0/0"}, - }, - }, - { // Services are running, but no active nodes were found. On API v1.41 // and up, the ServiceStatus is sent by the daemon, so this should not // affect the results. - doc: "API v1.41 no active nodes", + doc: "no active nodes", opts: clusterOpts{ - apiVersion: "1.41", activeNodes: 0, runningTasks: 2, desiredTasks: 4, @@ -127,9 +79,8 @@ func TestServiceListServiceStatus(t *testing.T) { }, }, { - doc: "API v1.41 3 active nodes, 1 task running", + doc: "active nodes, 1 task running", opts: clusterOpts{ - apiVersion: "1.41", activeNodes: 3, runningTasks: 1, desiredTasks: 2, @@ -141,9 +92,8 @@ func TestServiceListServiceStatus(t *testing.T) { }, }, { - doc: "API v1.41 3 active nodes, all tasks running", + doc: "active nodes, all tasks running", opts: clusterOpts{ - apiVersion: "1.41", activeNodes: 3, runningTasks: 3, desiredTasks: 3, @@ -174,7 +124,7 @@ func TestServiceListServiceStatus(t *testing.T) { } cli := test.NewFakeCli(&fakeClient{ serviceListFunc: func(ctx context.Context, options client.ServiceListOptions) ([]swarm.Service, error) { - if !options.Status || versions.LessThan(tc.opts.apiVersion, "1.41") { + if !options.Status { // Don't return "ServiceStatus" if not requested, or on older API versions for i := range tc.cluster.services { tc.cluster.services[i].ServiceStatus = nil @@ -214,7 +164,6 @@ func TestServiceListServiceStatus(t *testing.T) { } type clusterOpts struct { - apiVersion string activeNodes uint64 desiredTasks uint64 runningTasks uint64 diff --git a/cli/command/service/rollback.go b/cli/command/service/rollback.go index 4957ea517a1e..c781a45707bb 100644 --- a/cli/command/service/rollback.go +++ b/cli/command/service/rollback.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/moby/moby/api/types/versions" "github.com/moby/moby/client" "github.com/spf13/cobra" ) @@ -54,7 +53,7 @@ func runRollback(ctx context.Context, dockerCLI command.Cli, options *serviceOpt _, _ = fmt.Fprintln(dockerCLI.Out(), serviceID) - if options.detach || versions.LessThan(apiClient.ClientVersion(), "1.29") { + if options.detach { return nil } diff --git a/cli/command/service/rollback_test.go b/cli/command/service/rollback_test.go index 9c378a3db384..5f43332ddc2a 100644 --- a/cli/command/service/rollback_test.go +++ b/cli/command/service/rollback_test.go @@ -48,7 +48,8 @@ func TestRollback(t *testing.T) { }) cmd := newRollbackCommand(cli) cmd.SetArgs(tc.args) - cmd.Flags().Set("quiet", "true") + assert.NilError(t, cmd.Flags().Set("quiet", "true")) + assert.NilError(t, cmd.Flags().Set("detach", "true")) cmd.SetOut(io.Discard) assert.NilError(t, cmd.Execute()) assert.Check(t, is.Equal(strings.TrimSpace(cli.ErrBuffer().String()), tc.expectedDockerCliErr)) diff --git a/cli/command/service/scale.go b/cli/command/service/scale.go index 34efd283055a..9cfb5f94f11c 100644 --- a/cli/command/service/scale.go +++ b/cli/command/service/scale.go @@ -9,7 +9,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/moby/moby/api/types/versions" "github.com/moby/moby/client" "github.com/spf13/cobra" ) @@ -83,7 +82,7 @@ func runScale(ctx context.Context, dockerCLI command.Cli, options *scaleOptions, serviceIDs = append(serviceIDs, serviceID) } - if len(serviceIDs) > 0 && !options.detach && versions.GreaterThanOrEqualTo(dockerCLI.Client().ClientVersion(), "1.29") { + if len(serviceIDs) > 0 && !options.detach { for _, serviceID := range serviceIDs { if err := WaitOnService(ctx, dockerCLI, serviceID, false); err != nil { errs = append(errs, fmt.Errorf("%s: %v", serviceID, err)) diff --git a/cli/command/service/update.go b/cli/command/service/update.go index e4ffd4ceb785..4292e66be055 100644 --- a/cli/command/service/update.go +++ b/cli/command/service/update.go @@ -22,7 +22,6 @@ import ( "github.com/moby/moby/api/types/mount" "github.com/moby/moby/api/types/network" "github.com/moby/moby/api/types/swarm" - "github.com/moby/moby/api/types/versions" "github.com/moby/moby/client" "github.com/moby/swarmkit/v2/api/defaults" "github.com/spf13/cobra" @@ -165,14 +164,6 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, return err } - // There are two ways to do user-requested rollback. The old way is - // client-side, but with a sufficiently recent daemon we prefer - // server-side, because it will honor the rollback parameters. - var ( - clientSideRollback bool - serverSideRollback bool - ) - spec := &service.Spec if rollback { // Rollback can't be combined with other flags. @@ -188,20 +179,10 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, if otherFlagsPassed { return errors.New("other flags may not be combined with --rollback") } - - if versions.LessThan(apiClient.ClientVersion(), "1.28") { - clientSideRollback = true - spec = service.PreviousSpec - if spec == nil { - return errors.New("service does not have a previous specification to roll back to") - } - } else { - serverSideRollback = true - } } updateOpts := client.ServiceUpdateOptions{} - if serverSideRollback { + if rollback { updateOpts.Rollback = "previous" } @@ -214,9 +195,7 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, if err := resolveServiceImageDigestContentTrust(dockerCLI, spec); err != nil { return err } - if !options.noResolveImage && versions.GreaterThanOrEqualTo(apiClient.ClientVersion(), "1.30") { - updateOpts.QueryRegistry = true - } + updateOpts.QueryRegistry = !options.noResolveImage } updatedSecrets, err := getUpdatedSecrets(ctx, apiClient, flags, spec.TaskTemplate.ContainerSpec.Secrets) @@ -243,8 +222,7 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, if err != nil { return err } - switch { - case sendAuth: + if sendAuth { // Retrieve encoded auth token from the image reference // This would be the old image if it didn't change in this update image := spec.TaskTemplate.ContainerSpec.Image @@ -253,9 +231,7 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, return err } updateOpts.EncodedRegistryAuth = encodedAuth - case clientSideRollback: - updateOpts.RegistryAuthFrom = swarm.RegistryAuthFromPreviousSpec - default: + } else { updateOpts.RegistryAuthFrom = swarm.RegistryAuthFromSpec } @@ -270,7 +246,7 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, _, _ = fmt.Fprintln(dockerCLI.Out(), serviceID) - if options.detach || versions.LessThan(apiClient.ClientVersion(), "1.29") { + if options.detach { return nil } diff --git a/cli/command/stack/client_test.go b/cli/command/stack/client_test.go index 8c2c98f409b5..4c4a30d0b759 100644 --- a/cli/command/stack/client_test.go +++ b/cli/command/stack/client_test.go @@ -14,8 +14,6 @@ import ( type fakeClient struct { client.Client - version string - services []string networks []string secrets []string @@ -49,8 +47,8 @@ func (*fakeClient) ServerVersion(context.Context) (types.Version, error) { }, nil } -func (cli *fakeClient) ClientVersion() string { - return cli.version +func (*fakeClient) ClientVersion() string { + return client.MaxAPIVersion } func (cli *fakeClient) ServiceList(_ context.Context, options client.ServiceListOptions) ([]swarm.Service, error) { diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index 2d3427be8c69..944e6ca5108a 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/cli/compose/convert" composetypes "github.com/docker/cli/cli/compose/types" "github.com/moby/moby/api/types/swarm" - "github.com/moby/moby/api/types/versions" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -81,13 +80,6 @@ func runDeploy(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, return fmt.Errorf("invalid option %s for flag --resolve-image", opts.resolveImage) } - // client side image resolution should not be done when the supported - // server version is older than 1.30 - if versions.LessThan(dockerCLI.Client().ClientVersion(), "1.30") { - // TODO(thaJeztah): should this error if "opts.ResolveImage" is already other (unsupported) values? - opts.resolveImage = resolveImageNever - } - if opts.detach && !flags.Changed("detach") { _, _ = fmt.Fprintln(dockerCLI.Err(), "Since --detach=false was not specified, tasks will be created in the background.\n"+ "In a future release, --detach=false will become the default.") diff --git a/cli/command/stack/remove.go b/cli/command/stack/remove.go index c5ef0e219445..0484847aca2e 100644 --- a/cli/command/stack/remove.go +++ b/cli/command/stack/remove.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/cli/command" "github.com/moby/moby/api/types/network" "github.com/moby/moby/api/types/swarm" - "github.com/moby/moby/api/types/versions" "github.com/moby/moby/client" "github.com/spf13/cobra" ) @@ -61,20 +60,14 @@ func runRemove(ctx context.Context, dockerCli command.Cli, opts removeOptions) e return err } - var secrets []swarm.Secret - if versions.GreaterThanOrEqualTo(apiClient.ClientVersion(), "1.25") { - secrets, err = getStackSecrets(ctx, apiClient, namespace) - if err != nil { - return err - } + secrets, err := getStackSecrets(ctx, apiClient, namespace) + if err != nil { + return err } - var configs []swarm.Config - if versions.GreaterThanOrEqualTo(apiClient.ClientVersion(), "1.30") { - configs, err = getStackConfigs(ctx, apiClient, namespace) - if err != nil { - return err - } + configs, err := getStackConfigs(ctx, apiClient, namespace) + if err != nil { + return err } if len(services)+len(networks)+len(secrets)+len(configs) == 0 { diff --git a/cli/command/stack/remove_test.go b/cli/command/stack/remove_test.go index 6c843e6f6ecb..7c5d4033a531 100644 --- a/cli/command/stack/remove_test.go +++ b/cli/command/stack/remove_test.go @@ -11,7 +11,7 @@ import ( is "gotest.tools/v3/assert/cmp" ) -func fakeClientForRemoveStackTest(version string) *fakeClient { +func fakeClientForRemoveStackTest() *fakeClient { allServices := []string{ objectName("foo", "service1"), objectName("foo", "service2"), @@ -33,7 +33,6 @@ func fakeClientForRemoveStackTest(version string) *fakeClient { objectName("bar", "config1"), } return &fakeClient{ - version: version, services: allServices, networks: allNetworks, secrets: allSecrets, @@ -50,40 +49,16 @@ func TestRemoveWithEmptyName(t *testing.T) { assert.ErrorContains(t, cmd.Execute(), `invalid stack name: "' '"`) } -func TestRemoveStackVersion124DoesNotRemoveConfigsOrSecrets(t *testing.T) { - client := fakeClientForRemoveStackTest("1.24") - cmd := newRemoveCommand(test.NewFakeCli(client)) - cmd.SetArgs([]string{"foo", "bar"}) - - assert.NilError(t, cmd.Execute()) - assert.Check(t, is.DeepEqual(buildObjectIDs(client.services), client.removedServices)) - assert.Check(t, is.DeepEqual(buildObjectIDs(client.networks), client.removedNetworks)) - assert.Check(t, is.Len(client.removedSecrets, 0)) - assert.Check(t, is.Len(client.removedConfigs, 0)) -} - -func TestRemoveStackVersion125DoesNotRemoveConfigs(t *testing.T) { - client := fakeClientForRemoveStackTest("1.25") - cmd := newRemoveCommand(test.NewFakeCli(client)) - cmd.SetArgs([]string{"foo", "bar"}) - - assert.NilError(t, cmd.Execute()) - assert.Check(t, is.DeepEqual(buildObjectIDs(client.services), client.removedServices)) - assert.Check(t, is.DeepEqual(buildObjectIDs(client.networks), client.removedNetworks)) - assert.Check(t, is.DeepEqual(buildObjectIDs(client.secrets), client.removedSecrets)) - assert.Check(t, is.Len(client.removedConfigs, 0)) -} - -func TestRemoveStackVersion130RemovesEverything(t *testing.T) { - client := fakeClientForRemoveStackTest("1.30") - cmd := newRemoveCommand(test.NewFakeCli(client)) +func TestRemoveStackRemovesEverything(t *testing.T) { + apiClient := fakeClientForRemoveStackTest() + cmd := newRemoveCommand(test.NewFakeCli(apiClient)) cmd.SetArgs([]string{"foo", "bar"}) assert.NilError(t, cmd.Execute()) - assert.Check(t, is.DeepEqual(buildObjectIDs(client.services), client.removedServices)) - assert.Check(t, is.DeepEqual(buildObjectIDs(client.networks), client.removedNetworks)) - assert.Check(t, is.DeepEqual(buildObjectIDs(client.secrets), client.removedSecrets)) - assert.Check(t, is.DeepEqual(buildObjectIDs(client.configs), client.removedConfigs)) + assert.Check(t, is.DeepEqual(buildObjectIDs(apiClient.services), apiClient.removedServices)) + assert.Check(t, is.DeepEqual(buildObjectIDs(apiClient.networks), apiClient.removedNetworks)) + assert.Check(t, is.DeepEqual(buildObjectIDs(apiClient.secrets), apiClient.removedSecrets)) + assert.Check(t, is.DeepEqual(buildObjectIDs(apiClient.configs), apiClient.removedConfigs)) } func TestRemoveStackSkipEmpty(t *testing.T) { @@ -100,7 +75,6 @@ func TestRemoveStackSkipEmpty(t *testing.T) { allConfigIDs := buildObjectIDs(allConfigs) apiClient := &fakeClient{ - version: "1.30", services: allServices, networks: allNetworks, secrets: allSecrets, @@ -141,7 +115,6 @@ func TestRemoveContinueAfterError(t *testing.T) { removedServices := []string{} apiClient := &fakeClient{ - version: "1.30", services: allServices, networks: allNetworks, secrets: allSecrets, diff --git a/cli/command/system/prune_test.go b/cli/command/system/prune_test.go index b8c2f23f2e06..e2ee7cc9e8b9 100644 --- a/cli/command/system/prune_test.go +++ b/cli/command/system/prune_test.go @@ -22,24 +22,8 @@ import ( _ "github.com/docker/cli/cli/command/volume" ) -func TestPrunePromptPre131DoesNotIncludeBuildCache(t *testing.T) { - cli := test.NewFakeCli(&fakeClient{version: "1.30"}) - cmd := newPruneCommand(cli) - cmd.SetArgs([]string{}) - cmd.SetOut(io.Discard) - cmd.SetErr(io.Discard) - assert.ErrorContains(t, cmd.Execute(), "system prune has been cancelled") - expected := `WARNING! This will remove: - - all stopped containers - - all networks not used by at least one container - - all dangling images - -Are you sure you want to continue? [y/N] ` - assert.Check(t, is.Equal(expected, cli.OutBuffer().String())) -} - func TestPrunePromptFilters(t *testing.T) { - cli := test.NewFakeCli(&fakeClient{version: "1.31"}) + cli := test.NewFakeCli(&fakeClient{version: "1.51"}) cli.SetConfigFile(&configfile.ConfigFile{ PruneFilters: []string{"label!=never=remove-me", "label=remove=me"}, }) diff --git a/cli/command/volume/prune.go b/cli/command/volume/prune.go index 9b9ffdf805c8..d174c65b5a6a 100644 --- a/cli/command/volume/prune.go +++ b/cli/command/volume/prune.go @@ -11,7 +11,6 @@ import ( "github.com/docker/cli/internal/prompt" "github.com/docker/cli/opts" "github.com/docker/go-units" - "github.com/moby/moby/api/types/versions" "github.com/spf13/cobra" ) @@ -72,16 +71,11 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) pruneFilters := command.PruneFilters(dockerCli, options.filter.Value()) warning := unusedVolumesWarning - if versions.GreaterThanOrEqualTo(dockerCli.CurrentVersion(), "1.42") { - if options.all { - if _, ok := pruneFilters["all"]; ok { - return 0, "", invalidParamErr{errors.New("conflicting options: cannot specify both --all and --filter all=1")} - } - pruneFilters.Add("all", "true") - warning = allVolumesWarning + if options.all { + if _, ok := pruneFilters["all"]; ok { + return 0, "", invalidParamErr{errors.New("conflicting options: cannot specify both --all and --filter all=1")} } - } else { - // API < v1.42 removes all volumes (anonymous and named) by default. + pruneFilters.Add("all", "true") warning = allVolumesWarning } if !options.force { diff --git a/cli/compose/convert/service.go b/cli/compose/convert/service.go index 5ae8e122e9a7..746a9656f37e 100644 --- a/cli/compose/convert/service.go +++ b/cli/compose/convert/service.go @@ -16,7 +16,6 @@ import ( "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/network" "github.com/moby/moby/api/types/swarm" - "github.com/moby/moby/api/types/versions" "github.com/moby/moby/client" ) @@ -44,7 +43,7 @@ func Services( return nil, fmt.Errorf("service %s: %w", service.Name, err) } - serviceSpec, err := Service(apiClient.ClientVersion(), namespace, service, config.Networks, config.Volumes, secrets, configs) + serviceSpec, err := Service(namespace, service, config.Networks, config.Volumes, secrets, configs) if err != nil { return nil, fmt.Errorf("service %s: %w", service.Name, err) } @@ -56,7 +55,6 @@ func Services( // Service converts a ServiceConfig into a swarm ServiceSpec func Service( - apiVersion string, namespace Namespace, service composetypes.ServiceConfig, networkConfigs map[string]composetypes.NetworkConfig, @@ -161,6 +159,7 @@ func Service( Preferences: getPlacementPreference(service.Deploy.Placement.Preferences), MaxReplicas: service.Deploy.Placement.MaxReplicas, }, + Networks: networks, }, EndpointSpec: endpoint, Mode: mode, @@ -171,18 +170,6 @@ func Service( // add an image label to serviceSpec serviceSpec.Labels[LabelImage] = service.Image - // ServiceSpec.Networks is deprecated and should not have been used by - // this package. It is possible to update TaskTemplate.Networks, but it - // is not possible to update ServiceSpec.Networks. Unfortunately, we - // can't unconditionally start using TaskTemplate.Networks, because that - // will break with older daemons that don't support migrating from - // ServiceSpec.Networks to TaskTemplate.Networks. So which field to use - // is conditional on daemon version. - if versions.LessThan(apiVersion, "1.29") { - serviceSpec.Networks = networks //nolint:staticcheck // ignore SA1019: field is deprecated. - } else { - serviceSpec.TaskTemplate.Networks = networks - } return serviceSpec, nil } @@ -670,7 +657,6 @@ func toNetipAddrSlice(ips []string) []netip.Addr { func convertCredentialSpec(namespace Namespace, spec composetypes.CredentialSpecConfig, refs []*swarm.ConfigReference) (*swarm.CredentialSpec, error) { var o []string - // Config was added in API v1.40 if spec.Config != "" { o = append(o, `"Config"`) } diff --git a/cli/compose/convert/service_test.go b/cli/compose/convert/service_test.go index 165e5bf99c4c..db094f8a6bdf 100644 --- a/cli/compose/convert/service_test.go +++ b/cli/compose/convert/service_test.go @@ -495,7 +495,7 @@ func TestServiceConvertsIsolation(t *testing.T) { src := composetypes.ServiceConfig{ Isolation: "hyperv", } - result, err := Service("1.35", Namespace{name: "foo"}, src, nil, nil, nil, nil) + result, err := Service(Namespace{name: "foo"}, src, nil, nil, nil, nil) assert.NilError(t, err) assert.Check(t, is.Equal(container.IsolationHyperV, result.TaskTemplate.ContainerSpec.Isolation)) } @@ -692,7 +692,7 @@ func TestConvertServiceCapAddAndCapDrop(t *testing.T) { } for _, tc := range tests { t.Run(tc.title, func(t *testing.T) { - result, err := Service("1.41", Namespace{name: "foo"}, tc.in, nil, nil, nil, nil) + result, err := Service(Namespace{name: "foo"}, tc.in, nil, nil, nil, nil) assert.NilError(t, err) assert.Check(t, is.DeepEqual(result.TaskTemplate.ContainerSpec.CapabilityAdd, tc.out.CapAdd)) assert.Check(t, is.DeepEqual(result.TaskTemplate.ContainerSpec.CapabilityDrop, tc.out.CapDrop)) diff --git a/e2e/container/run_test.go b/e2e/container/run_test.go index 55382ad465e4..af0486fdeeea 100644 --- a/e2e/container/run_test.go +++ b/e2e/container/run_test.go @@ -210,7 +210,6 @@ func TestRunWithCgroupNamespace(t *testing.T) { func TestMountSubvolume(t *testing.T) { skip.If(t, versions.LessThan(environment.DaemonAPIVersion(t), "1.45")) - volName := "test-volume-" + t.Name() icmd.RunCommand("docker", "volume", "create", volName).Assert(t, icmd.Success) diff --git a/e2e/global/cli_test.go b/e2e/global/cli_test.go index 5275ee945811..036a965194fd 100644 --- a/e2e/global/cli_test.go +++ b/e2e/global/cli_test.go @@ -16,7 +16,6 @@ import ( "github.com/docker/cli/e2e/testutils" "github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test/environment" - "github.com/moby/moby/api/types/versions" "gotest.tools/v3/assert" "gotest.tools/v3/icmd" "gotest.tools/v3/poll" @@ -144,7 +143,6 @@ func TestPromptExitCode(t *testing.T) { run: func(t *testing.T) icmd.Cmd { t.Helper() t.Skip("flaky test: see https://github.com/docker/cli/issues/6248") - skip.If(t, versions.LessThan(environment.DaemonAPIVersion(t), "1.44")) const plugin = "registry:5000/plugin-install-test:latest" @@ -162,7 +160,6 @@ func TestPromptExitCode(t *testing.T) { run: func(t *testing.T) icmd.Cmd { t.Helper() t.Skip("flaky test: see https://github.com/docker/cli/issues/6248") - skip.If(t, versions.LessThan(environment.DaemonAPIVersion(t), "1.44")) const plugin = "registry:5000/plugin-upgrade-test" From f046bd371a23ddc8950df8a84463e8a5bc764c9a Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 10 Oct 2025 21:03:50 +0200 Subject: [PATCH 2/5] cli/command/container: rm use of deprecated MacAddress field This field is no longer in use since API v1.44. Signed-off-by: Sebastiaan van Stijn --- cli/command/container/opts.go | 21 ++++++--------------- cli/command/container/opts_test.go | 12 ++---------- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index 46c41850a8a1..2c39d56f1bff 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -659,7 +659,6 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con Cmd: runCmd, Image: copts.Image, Volumes: volumes, - MacAddress: copts.macAddress, Entrypoint: entrypoint, WorkingDir: copts.workingDir, Labels: opts.ConvertKVStringsToMap(labels), @@ -730,25 +729,17 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con config.StdinOnce = true } - networkingConfig := &network.NetworkingConfig{ - EndpointsConfig: make(map[string]*network.EndpointSettings), - } - - networkingConfig.EndpointsConfig, err = parseNetworkOpts(copts) + epCfg, err := parseNetworkOpts(copts) if err != nil { return nil, err } - // Put the endpoint-specific MacAddress of the "main" network attachment into the container Config for backward - // compatibility with older daemons. - if nw, ok := networkingConfig.EndpointsConfig[hostConfig.NetworkMode.NetworkName()]; ok { - config.MacAddress = nw.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44. - } - return &containerConfig{ - Config: config, - HostConfig: hostConfig, - NetworkingConfig: networkingConfig, + Config: config, + HostConfig: hostConfig, + NetworkingConfig: &network.NetworkingConfig{ + EndpointsConfig: epCfg, + }, }, nil } diff --git a/cli/command/container/opts_test.go b/cli/command/container/opts_test.go index 68cbd7ef9df3..352c38d07113 100644 --- a/cli/command/container/opts_test.go +++ b/cli/command/container/opts_test.go @@ -351,11 +351,7 @@ func TestParseWithMacAddress(t *testing.T) { if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" { t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err) } - config, hostConfig, nwConfig := mustParse(t, validMacAddress) - if config.MacAddress != "92:d0:c6:0a:29:33" { //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44. - t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as container-wide MacAddress, got '%v'", - config.MacAddress) //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44. - } + _, hostConfig, nwConfig := mustParse(t, validMacAddress) defaultNw := hostConfig.NetworkMode.NetworkName() if nwConfig.EndpointsConfig[defaultNw].MacAddress != "92:d0:c6:0a:29:33" { t.Fatalf("Expected the default endpoint to have the MacAddress '92:d0:c6:0a:29:33' set, got '%v'", nwConfig.EndpointsConfig[defaultNw].MacAddress) @@ -576,7 +572,6 @@ func TestParseNetworkConfig(t *testing.T) { name string flags []string expected map[string]*networktypes.EndpointSettings - expectedCfg container.Config expectedHostCfg container.HostConfig expectedErr string }{ @@ -680,7 +675,6 @@ func TestParseNetworkConfig(t *testing.T) { MacAddress: "02:32:1c:23:00:04", }, }, - expectedCfg: container.Config{MacAddress: "02:32:1c:23:00:04"}, expectedHostCfg: container.HostConfig{NetworkMode: "net1"}, }, { @@ -698,7 +692,6 @@ func TestParseNetworkConfig(t *testing.T) { MacAddress: "52:0f:f3:dc:50:10", }, }, - expectedCfg: container.Config{MacAddress: "52:0f:f3:dc:50:10"}, expectedHostCfg: container.HostConfig{NetworkMode: "net1"}, }, { @@ -745,7 +738,7 @@ func TestParseNetworkConfig(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - config, hConfig, nwConfig, err := parseRun(tc.flags) + _, hConfig, nwConfig, err := parseRun(tc.flags) if tc.expectedErr != "" { assert.Error(t, err, tc.expectedErr) @@ -753,7 +746,6 @@ func TestParseNetworkConfig(t *testing.T) { } assert.NilError(t, err) - assert.DeepEqual(t, config.MacAddress, tc.expectedCfg.MacAddress) //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44. assert.DeepEqual(t, hConfig.NetworkMode, tc.expectedHostCfg.NetworkMode) assert.DeepEqual(t, nwConfig.EndpointsConfig, tc.expected, cmpopts.EquateComparable(netip.Addr{})) }) From b8f2b7c678d00b5617590e0c1900988d348c70ca Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 10 Oct 2025 20:39:37 +0200 Subject: [PATCH 3/5] cli/command/service: remove AppendServiceStatus (API --- cli/command/image/opts.go | 17 ---- cli/command/service/list.go | 123 +--------------------------- cli/command/stack/services_test.go | 27 ------ cli/command/stack/services_utils.go | 34 +------- 4 files changed, 6 insertions(+), 195 deletions(-) delete mode 100644 cli/command/image/opts.go diff --git a/cli/command/image/opts.go b/cli/command/image/opts.go deleted file mode 100644 index 37378b6ece65..000000000000 --- a/cli/command/image/opts.go +++ /dev/null @@ -1,17 +0,0 @@ -package image - -import ( - "os" - - "github.com/spf13/pflag" -) - -// addPlatformFlag adds "--platform" to a set of flags for API version 1.32 and -// later, using the value of "DOCKER_DEFAULT_PLATFORM" (if set) as a default. -// -// It should not be used for new uses, which may have a different API version -// requirement. -func addPlatformFlag(flags *pflag.FlagSet, target *string) { - flags.StringVar(target, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") - _ = flags.SetAnnotation("platform", "version", []string{"1.32"}) -} diff --git a/cli/command/service/list.go b/cli/command/service/list.go index 95254fd9c826..cac25d8e4f94 100644 --- a/cli/command/service/list.go +++ b/cli/command/service/list.go @@ -8,7 +8,6 @@ import ( "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/opts" - "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/client" "github.com/spf13/cobra" ) @@ -43,44 +42,19 @@ func newListCommand(dockerCLI command.Cli) *cobra.Command { } func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) error { - var ( - apiClient = dockerCLI.Client() - err error - ) - - listOpts := client.ServiceListOptions{ + apiClient := dockerCLI.Client() + services, err := apiClient.ServiceList(ctx, client.ServiceListOptions{ Filters: options.filter.Value(), // When not running "quiet", also get service status (number of running // and desired tasks). Note that this is only supported on API v1.41 and // up; older API versions ignore this option, and we will have to collect // the information manually below. Status: !options.quiet, - } - - services, err := apiClient.ServiceList(ctx, listOpts) + }) if err != nil { return err } - if listOpts.Status { - // Now that a request was made, we know what API version was used (either - // through configuration, or after client and daemon negotiated a version). - // If API version v1.41 or up was used; the daemon should already have done - // the legwork for us, and we don't have to calculate the number of desired - // and running tasks. On older API versions, we need to do some extra requests - // to get that information. - // - // So theoretically, this step can be skipped based on API version, however, - // some of our unit tests don't set the API version, and there may be other - // situations where the client uses the "default" version. To account for - // these situations, we do a quick check for services that do not have - // a ServiceStatus set, and perform a lookup for those. - services, err = AppendServiceStatus(ctx, apiClient, services) - if err != nil { - return err - } - } - format := options.format if len(format) == 0 { if len(dockerCLI.ConfigFile().ServicesFormat) > 0 && !options.quiet { @@ -96,94 +70,3 @@ func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) er } return ListFormatWrite(servicesCtx, services) } - -// AppendServiceStatus propagates the ServiceStatus field for "services". -// -// If API version v1.41 or up is used, this information is already set by the -// daemon. On older API versions, we need to do some extra requests to get -// that information. Theoretically, this function can be skipped based on API -// version, however, some of our unit tests don't set the API version, and -// there may be other situations where the client uses the "default" version. -// To take these situations into account, we do a quick check for services -// that don't have ServiceStatus set, and perform a lookup for those. -func AppendServiceStatus(ctx context.Context, c client.APIClient, services []swarm.Service) ([]swarm.Service, error) { - status := map[string]*swarm.ServiceStatus{} - taskFilter := make(client.Filters) - for i, s := range services { - // there is no need in this switch to check for job modes. jobs are not - // supported until after ServiceStatus was introduced. - switch { - case s.ServiceStatus != nil: - // Server already returned service-status, so we don't - // have to look-up tasks for this service. - continue - case s.Spec.Mode.Replicated != nil: - // For replicated services, set the desired number of tasks; - // that way we can present this information in case we're unable - // to get a list of tasks from the server. - services[i].ServiceStatus = &swarm.ServiceStatus{DesiredTasks: *s.Spec.Mode.Replicated.Replicas} - status[s.ID] = &swarm.ServiceStatus{} - taskFilter.Add("service", s.ID) - case s.Spec.Mode.Global != nil: - // No such thing as number of desired tasks for global services - services[i].ServiceStatus = &swarm.ServiceStatus{} - status[s.ID] = &swarm.ServiceStatus{} - taskFilter.Add("service", s.ID) - default: - // Unknown task type - } - } - if len(status) == 0 { - // All services have their ServiceStatus set, so we're done - return services, nil - } - - tasks, err := c.TaskList(ctx, client.TaskListOptions{Filters: taskFilter}) - if err != nil { - return nil, err - } - if len(tasks) == 0 { - return services, nil - } - activeNodes, err := getActiveNodes(ctx, c) - if err != nil { - return nil, err - } - - for _, task := range tasks { - if status[task.ServiceID] == nil { - // This should not happen in practice; either all services have - // a ServiceStatus set, or none of them. - continue - } - // TODO: this should only be needed for "global" services. Replicated - // services have `Spec.Mode.Replicated.Replicas`, which should give this value. - if task.DesiredState != swarm.TaskStateShutdown { - status[task.ServiceID].DesiredTasks++ - } - if _, nodeActive := activeNodes[task.NodeID]; nodeActive && task.Status.State == swarm.TaskStateRunning { - status[task.ServiceID].RunningTasks++ - } - } - - for i, service := range services { - if s := status[service.ID]; s != nil { - services[i].ServiceStatus = s - } - } - return services, nil -} - -func getActiveNodes(ctx context.Context, c client.NodeAPIClient) (map[string]struct{}, error) { - nodes, err := c.NodeList(ctx, client.NodeListOptions{}) - if err != nil { - return nil, err - } - activeNodes := make(map[string]struct{}) - for _, n := range nodes { - if n.Status.State != swarm.NodeStateDown { - activeNodes[n.ID] = struct{}{} - } - } - return activeNodes, nil -} diff --git a/cli/command/stack/services_test.go b/cli/command/stack/services_test.go index 0fe5b5fc8e0a..8326b335e4da 100644 --- a/cli/command/stack/services_test.go +++ b/cli/command/stack/services_test.go @@ -21,8 +21,6 @@ func TestStackServicesErrors(t *testing.T) { args []string flags map[string]string serviceListFunc func(options client.ServiceListOptions) ([]swarm.Service, error) - nodeListFunc func(options client.NodeListOptions) ([]swarm.Node, error) - taskListFunc func(options client.TaskListOptions) ([]swarm.Task, error) expectedError string }{ { @@ -32,29 +30,6 @@ func TestStackServicesErrors(t *testing.T) { }, expectedError: "error getting services", }, - { - args: []string{"foo"}, - serviceListFunc: func(options client.ServiceListOptions) ([]swarm.Service, error) { - return []swarm.Service{*builders.Service(builders.GlobalService())}, nil - }, - nodeListFunc: func(options client.NodeListOptions) ([]swarm.Node, error) { - return nil, errors.New("error getting nodes") - }, - taskListFunc: func(options client.TaskListOptions) ([]swarm.Task, error) { - return []swarm.Task{*builders.Task()}, nil - }, - expectedError: "error getting nodes", - }, - { - args: []string{"foo"}, - serviceListFunc: func(options client.ServiceListOptions) ([]swarm.Service, error) { - return []swarm.Service{*builders.Service(builders.GlobalService())}, nil - }, - taskListFunc: func(options client.TaskListOptions) ([]swarm.Task, error) { - return nil, errors.New("error getting tasks") - }, - expectedError: "error getting tasks", - }, { args: []string{"foo"}, flags: map[string]string{ @@ -71,8 +46,6 @@ func TestStackServicesErrors(t *testing.T) { t.Run(tc.expectedError, func(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ serviceListFunc: tc.serviceListFunc, - nodeListFunc: tc.nodeListFunc, - taskListFunc: tc.taskListFunc, }) cmd := newServicesCommand(cli) cmd.SetArgs(tc.args) diff --git a/cli/command/stack/services_utils.go b/cli/command/stack/services_utils.go index 4fd10f2d30cd..57718f037a1d 100644 --- a/cli/command/stack/services_utils.go +++ b/cli/command/stack/services_utils.go @@ -3,44 +3,16 @@ package stack import ( "context" - "github.com/docker/cli/cli/command/service" "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/client" ) // getServices is the swarm implementation of listing stack services func getServices(ctx context.Context, apiClient client.APIClient, opts serviceListOptions) ([]swarm.Service, error) { - listOpts := client.ServiceListOptions{ + return apiClient.ServiceList(ctx, client.ServiceListOptions{ Filters: getStackFilterFromOpt(opts.namespace, opts.filter), // When not running "quiet", also get service status (number of running - // and desired tasks). Note that this is only supported on API v1.41 and - // up; older API versions ignore this option, and we will have to collect - // the information manually below. + // and desired tasks). Status: !opts.quiet, - } - - services, err := apiClient.ServiceList(ctx, listOpts) - if err != nil { - return nil, err - } - - if listOpts.Status { - // Now that a request was made, we know what API version was used (either - // through configuration, or after client and daemon negotiated a version). - // If API version v1.41 or up was used; the daemon should already have done - // the legwork for us, and we don't have to calculate the number of desired - // and running tasks. On older API versions, we need to do some extra requests - // to get that information. - // - // So theoretically, this step can be skipped based on API version, however, - // some of our unit tests don't set the API version, and there may be other - // situations where the client uses the "default" version. To account for - // these situations, we do a quick check for services that do not have - // a ServiceStatus set, and perform a lookup for those. - services, err = service.AppendServiceStatus(ctx, apiClient, services) - if err != nil { - return nil, err - } - } - return services, nil + }) } From 4c73cefc1528d5b74af7d65aad5709826d85bdf9 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 10 Oct 2025 20:42:29 +0200 Subject: [PATCH 4/5] cli/command/container, image: remove addPlatformFlag utility Signed-off-by: Sebastiaan van Stijn --- cli/command/container/create.go | 3 ++- cli/command/container/opts.go | 10 ---------- cli/command/container/run.go | 4 +++- cli/command/image/import.go | 3 ++- cli/command/image/pull.go | 6 +++--- 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 55b09a22c1e2..13fa0a499816 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -84,7 +84,8 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command { flags.Bool("help", false, "Print usage") // TODO(thaJeztah): consider adding platform as "image create option" on containerOptions - addPlatformFlag(flags, &options.platform) + flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") + _ = flags.SetAnnotation("platform", "version", []string{"1.32"}) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms()) flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification") diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index 2c39d56f1bff..723537e87c7c 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -141,16 +141,6 @@ type containerOptions struct { Args []string } -// addPlatformFlag adds "--platform" to a set of flags for API version 1.32 and -// later, using the value of "DOCKER_DEFAULT_PLATFORM" (if set) as a default. -// -// It should not be used for new uses, which may have a different API version -// requirement. -func addPlatformFlag(flags *pflag.FlagSet, target *string) { - flags.StringVar(target, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") - _ = flags.SetAnnotation("platform", "version", []string{"1.32"}) -} - // addFlags adds all command line flags that will be used by parse to the FlagSet func addFlags(flags *pflag.FlagSet) *containerOptions { copts := &containerOptions{ diff --git a/cli/command/container/run.go b/cli/command/container/run.go index a8630f82563a..f2c634fd0161 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "os" "strings" "syscall" @@ -70,7 +71,8 @@ func newRunCommand(dockerCLI command.Cli) *cobra.Command { flags.Bool("help", false, "Print usage") // TODO(thaJeztah): consider adding platform as "image create option" on containerOptions - addPlatformFlag(flags, &options.platform) + flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") + _ = flags.SetAnnotation("platform", "version", []string{"1.32"}) flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification") copts = addFlags(flags) diff --git a/cli/command/image/import.go b/cli/command/image/import.go index 2a9f9d329fb1..8af28c8dee28 100644 --- a/cli/command/image/import.go +++ b/cli/command/image/import.go @@ -48,7 +48,8 @@ func newImportCommand(dockerCLI command.Cli) *cobra.Command { options.changes = dockeropts.NewListOpts(nil) flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image") flags.StringVarP(&options.message, "message", "m", "", "Set commit message for imported image") - addPlatformFlag(flags, &options.platform) + flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") + _ = flags.SetAnnotation("platform", "version", []string{"1.32"}) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms()) return cmd diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index 1bfbbed2dc62..7df515f522fc 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "os" "github.com/distribution/reference" "github.com/docker/cli/cli" @@ -54,10 +55,9 @@ func newPullCommand(dockerCLI command.Cli) *cobra.Command { flags.BoolVarP(&opts.all, "all-tags", "a", false, "Download all tagged images in the repository") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output") - - addPlatformFlag(flags, &opts.platform) flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification") - + flags.StringVar(&opts.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") + _ = flags.SetAnnotation("platform", "version", []string{"1.32"}) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms()) return cmd From 5ad91456c73f2a20f9efeabe547c1b251dbe73e8 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 10 Oct 2025 20:42:49 +0200 Subject: [PATCH 5/5] docs: update some versions in examples Signed-off-by: Sebastiaan van Stijn --- docs/reference/commandline/image_build.md | 26 +++---- docs/reference/commandline/version.md | 87 ++++++++++++----------- man/src/version.md | 40 +++++------ 3 files changed, 77 insertions(+), 76 deletions(-) diff --git a/docs/reference/commandline/image_build.md b/docs/reference/commandline/image_build.md index 62575c4e080f..db75ce398904 100644 --- a/docs/reference/commandline/image_build.md +++ b/docs/reference/commandline/image_build.md @@ -195,22 +195,22 @@ line in the `Engine` section: ```console Client: Docker Engine - Community - Version: 23.0.3 - API version: 1.42 - Go version: go1.19.7 - Git commit: 3e7cbfd - Built: Tue Apr 4 22:05:41 2023 - OS/Arch: darwin/amd64 - Context: default + Version: 28.5.1 + API version: 1.51 + Go version: go1.24.8 + Git commit: e180ab8 + Built: Wed Oct 8 12:16:17 2025 + OS/Arch: darwin/arm64 + Context: desktop-linux Server: Docker Engine - Community Engine: - Version: 23.0.3 - API version: 1.42 (minimum version 1.12) - Go version: go1.19.7 - Git commit: 59118bf - Built: Tue Apr 4 22:05:41 2023 - OS/Arch: linux/amd64 + Version: 28.5.1 + API version: 1.51 (minimum version 1.24) + Go version: go1.24.8 + Git commit: f8215cc + Built: Wed Oct 8 12:18:25 2025 + OS/Arch: linux/arm64 Experimental: true [...] ``` diff --git a/docs/reference/commandline/version.md b/docs/reference/commandline/version.md index 040ba1b5a436..1a19f88f1122 100644 --- a/docs/reference/commandline/version.md +++ b/docs/reference/commandline/version.md @@ -38,29 +38,29 @@ machine running Docker Desktop: $ docker version Client: Docker Engine - Community - Version: 23.0.3 - API version: 1.42 - Go version: go1.19.7 - Git commit: 3e7cbfd - Built: Tue Apr 4 22:05:41 2023 - OS/Arch: darwin/amd64 - Context: default + Version: 28.5.1 + API version: 1.51 + Go version: go1.24.8 + Git commit: e180ab8 + Built: Wed Oct 8 12:16:17 2025 + OS/Arch: darwin/arm64 + Context: remote-test-server Server: Docker Desktop 4.19.0 (12345) Engine: - Version: 23.0.3 - API version: 1.42 (minimum version 1.12) - Go version: go1.19.7 - Git commit: 59118bf - Built: Tue Apr 4 22:05:41 2023 + Version: 27.5.1 + API version: 1.47 (minimum version 1.24) + Go version: go1.22.11 + Git commit: 4c9b3b0 + Built: Wed Jan 22 13:41:24 2025 OS/Arch: linux/amd64 - Experimental: false + Experimental: true containerd: - Version: 1.6.20 - GitCommit: 2806fc1057397dbaeefbea0e4e17bddfbd388f38 + Version: v1.7.25 + GitCommit: bcc810d6b9066471b0b6fa75f557a15a1cbf31bb runc: - Version: 1.1.5 - GitCommit: v1.1.5-0-gf19387a + Version: 1.2.4 + GitCommit: v1.2.4-0-g6c52b3f docker-init: Version: 0.19.0 GitCommit: de40ad0 @@ -83,31 +83,32 @@ remote-test-server $ docker version Client: Docker Engine - Community - Version: 23.0.3 - API version: 1.40 (downgraded from 1.42) - Go version: go1.19.7 - Git commit: 3e7cbfd - Built: Tue Apr 4 22:05:41 2023 - OS/Arch: darwin/amd64 + Version: 28.5.1 + API version: 1.51 + Go version: go1.24.8 + Git commit: e180ab8 + Built: Wed Oct 8 12:16:17 2025 + OS/Arch: darwin/arm64 Context: remote-test-server Server: Docker Engine - Community Engine: - Version: 19.03.8 - API version: 1.40 (minimum version 1.12) - Go version: go1.12.17 - Git commit: afacb8b - Built: Wed Mar 11 01:29:16 2020 + Version: 27.5.1 + API version: 1.47 (minimum version 1.24) + Go version: go1.22.11 + Git commit: 4c9b3b0 + Built: Wed Jan 22 13:41:24 2025 OS/Arch: linux/amd64 + Experimental: true containerd: - Version: v1.2.13 - GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429 + Version: v1.7.25 + GitCommit: bcc810d6b9066471b0b6fa75f557a15a1cbf31bb runc: - Version: 1.0.0-rc10 - GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd + Version: 1.2.4 + GitCommit: v1.2.4-0-g6c52b3f docker-init: - Version: 0.18.0 - GitCommit: fec3683 + Version: 0.19.0 + GitCommit: de40ad0 ``` ### API version and version negotiation @@ -117,14 +118,14 @@ CLI is connecting with. When connecting with the Docker Engine, the Docker CLI and Docker Engine perform API version negotiation, and select the highest API version that is supported by both the Docker CLI and the Docker Engine. -For example, if the CLI is connecting with Docker Engine version 19.03, it downgrades -to API version 1.40 (refer to the [API version matrix](https://docs.docker.com/reference/api/engine/#api-version-matrix) +For example, if the CLI is connecting with Docker Engine version 27.5, it downgrades +to API version 1.47 (refer to the [API version matrix](https://docs.docker.com/reference/api/engine/#api-version-matrix) to learn about the supported API versions for Docker Engine): ```console $ docker version --format '{{.Client.APIVersion}}' -1.40 +1.47 ``` Be aware that API version can also be overridden using the `DOCKER_API_VERSION` @@ -135,14 +136,14 @@ variable removes the override, and re-enables API version negotiation: ```console $ env | grep DOCKER_API_VERSION -DOCKER_API_VERSION=1.39 +DOCKER_API_VERSION=1.50 $ docker version --format '{{.Client.APIVersion}}' -1.39 +1.50 $ unset DOCKER_API_VERSION $ docker version --format '{{.Client.APIVersion}}' -1.42 +1.51 ``` ## Examples @@ -159,7 +160,7 @@ page for details of the format. ```console $ docker version --format '{{.Server.Version}}' -23.0.3 +28.5.1 ``` ### Get the client API version @@ -169,7 +170,7 @@ The following example prints the API version that is used by the client: ```console $ docker version --format '{{.Client.APIVersion}}' -1.42 +1.51 ``` The version shown is the API version that is negotiated between the client @@ -181,5 +182,5 @@ above for more information. ```console $ docker version --format '{{json .}}' -{"Client":"Version":"23.0.3","ApiVersion":"1.42", ...} +{"Client":"Version":"28.5.1","ApiVersion":"1.51", ...} ``` diff --git a/man/src/version.md b/man/src/version.md index 4d5ff070e322..250f814ab4c1 100644 --- a/man/src/version.md +++ b/man/src/version.md @@ -17,29 +17,29 @@ machine running Docker Desktop: $ docker version Client: Docker Engine - Community - Version: 23.0.3 - API version: 1.42 - Go version: go1.19.7 - Git commit: 3e7cbfd - Built: Tue Apr 4 22:05:41 2023 - OS/Arch: darwin/amd64 - Context: default + Version: 28.5.1 + API version: 1.51 + Go version: go1.24.8 + Git commit: e180ab8 + Built: Wed Oct 8 12:16:17 2025 + OS/Arch: darwin/arm64 + Context: desktop-linux - Server: Docker Desktop 4.19.0 (12345) + Server: Docker Desktop 4.49.0 (12345) Engine: - Version: 23.0.3 - API version: 1.42 (minimum version 1.12) - Go version: go1.19.7 - Git commit: 59118bf - Built: Tue Apr 4 22:05:41 2023 - OS/Arch: linux/amd64 + Version: 28.5.1 + API version: 1.51 (minimum version 1.24) + Go version: go1.24.8 + Git commit: f8215cc + Built: Wed Oct 8 12:18:25 2025 + OS/Arch: linux/arm64 Experimental: false containerd: - Version: 1.6.20 - GitCommit: 2806fc1057397dbaeefbea0e4e17bddfbd388f38 + Version: 1.7.27 + GitCommit: 05044ec0a9a75232cad458027ca83437aae3f4da runc: - Version: 1.1.5 - GitCommit: v1.1.5-0-gf19387a + Version: 1.2.5 + GitCommit: v1.2.5-0-g59923ef docker-init: Version: 0.19.0 GitCommit: de40ad0 @@ -47,11 +47,11 @@ machine running Docker Desktop: Get server version: $ docker version --format '{{.Server.Version}}' - 23.0.3 + 28.5.1 Dump raw data: To view all available fields, you can use the format `{{json .}}`. $ docker version --format '{{json .}}' - {"Client":"Version":"23.0.3","ApiVersion":"1.42", ...} + {"Client":"Version":"28.5.1","ApiVersion":"1.51", ...}