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 2c5e0c876..a7c88dcc4 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 3fea15575..adc087a21 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 3faacc382..55b09a22c 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 925fc0d73..232d700a6 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 68ad421ae..5fa4541ef 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 d5830f5c9..c3c9565b1 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 479e9a953..5bb2f6a71 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 4957ea517..c781a4570 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 9c378a3db..5f43332dd 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 34efd2830..9cfb5f94f 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 e4ffd4ceb..4292e66be 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 8c2c98f40..4c4a30d0b 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 2d3427be8..944e6ca51 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 c5ef0e219..0484847ac 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 6c843e6f6..7c5d4033a 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)) +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.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)) - 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 b8c2f23f2..e2ee7cc9e 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 9b9ffdf80..d174c65b5 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 5ae8e122e..746a9656f 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 165e5bf99..db094f8a6 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 55382ad46..af0486fde 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 5275ee945..036a96519 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 46c41850a..2c39d56f1 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 68cbd7ef9..352c38d07 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 37378b6ec..000000000 --- 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 95254fd9c..cac25d8e4 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 0fe5b5fc8..8326b335e 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 4fd10f2d3..57718f037 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 55b09a22c..13fa0a499 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 2c39d56f1..723537e87 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 a8630f825..f2c634fd0 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 2a9f9d329..8af28c8de 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 1bfbbed2d..7df515f52 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 62575c4e0..db75ce398 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 040ba1b5a..1a19f88f1 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 4d5ff070e..250f814ab 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", ...}