From 3cd108fcd075a19cdbb6cd9ebb493d981409710e Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 29 Jul 2025 18:09:24 +0200 Subject: [PATCH 001/193] vendor: github.com/docker/docker v28.3.3 full diff: https://github.com/docker/docker/compare/v28.3.1...v28.3.3 Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 2 +- vendor.sum | 4 ++-- vendor/github.com/docker/docker/api/swagger.yaml | 3 ++- .../docker/docker/api/types/registry/authconfig.go | 2 ++ vendor/github.com/docker/docker/client/image_push.go | 11 ++++++++++- vendor/modules.txt | 2 +- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/vendor.mod b/vendor.mod index 68d14b00251c..d784ed55b6b5 100644 --- a/vendor.mod +++ b/vendor.mod @@ -15,7 +15,7 @@ require ( github.com/distribution/reference v0.6.0 github.com/docker/cli-docs-tool v0.10.0 github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.3.1+incompatible + github.com/docker/docker v28.3.3+incompatible github.com/docker/docker-credential-helpers v0.9.3 github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 diff --git a/vendor.sum b/vendor.sum index 3aa9706f4a08..7dc3d017d722 100644 --- a/vendor.sum +++ b/vendor.sum @@ -57,8 +57,8 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.3.1+incompatible h1:20+BmuA9FXlCX4ByQ0vYJcUEnOmRM6XljDnFWR+jCyY= -github.com/docker/docker v28.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= diff --git a/vendor/github.com/docker/docker/api/swagger.yaml b/vendor/github.com/docker/docker/api/swagger.yaml index 8d6a8f9356a4..3880635db128 100644 --- a/vendor/github.com/docker/docker/api/swagger.yaml +++ b/vendor/github.com/docker/docker/api/swagger.yaml @@ -2913,7 +2913,8 @@ definitions: be used. If multiple endpoints have the same priority, endpoints are lexicographically sorted based on their network name, and the one that sorts first is picked. - type: "number" + type: "integer" + format: "int64" example: - 10 diff --git a/vendor/github.com/docker/docker/api/types/registry/authconfig.go b/vendor/github.com/docker/docker/api/types/registry/authconfig.go index 70f732007211..fa9037bdadfd 100644 --- a/vendor/github.com/docker/docker/api/types/registry/authconfig.go +++ b/vendor/github.com/docker/docker/api/types/registry/authconfig.go @@ -83,6 +83,8 @@ func DecodeAuthConfig(authEncoded string) (*AuthConfig, error) { // Like [DecodeAuthConfig], this function always returns an [AuthConfig], even if an // error occurs. It is up to the caller to decide if authentication is required, // and if the error can be ignored. +// +// Deprecated: this function is no longer used and will be removed in the next release. func DecodeAuthConfigBody(rdr io.ReadCloser) (*AuthConfig, error) { return decodeAuthConfigFromReader(rdr) } diff --git a/vendor/github.com/docker/docker/client/image_push.go b/vendor/github.com/docker/docker/client/image_push.go index cbbe9a25d609..8dbe0b1e5caa 100644 --- a/vendor/github.com/docker/docker/client/image_push.go +++ b/vendor/github.com/docker/docker/client/image_push.go @@ -66,7 +66,16 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options image.Pu } func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (*http.Response, error) { - return cli.post(ctx, "/images/"+imageID+"/push", query, nil, http.Header{ + // Always send a body (which may be an empty JSON document ("{}")) to prevent + // EOF errors on older daemons which had faulty fallback code for handling + // authentication in the body when no auth-header was set, resulting in; + // + // Error response from daemon: bad parameters and missing X-Registry-Auth: invalid X-Registry-Auth header: EOF + // + // We use [http.NoBody], which gets marshaled to an empty JSON document. + // + // see: https://github.com/moby/moby/commit/ea29dffaa541289591aa44fa85d2a596ce860e16 + return cli.post(ctx, "/images/"+imageID+"/push", query, http.NoBody, http.Header{ registry.AuthHeader: {registryAuth}, }) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 1abd3baaaa5b..dae39cb3a8bc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/uuid -# github.com/docker/docker v28.3.1+incompatible +# github.com/docker/docker v28.3.3+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types From ce2a0a4ecba685e7da20379f5fe76560439591f4 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 29 Jul 2025 18:10:03 +0200 Subject: [PATCH 002/193] vendor: github.com/opencontainers/image-spec v1.1.1 full diff: https://github.com/opencontainers/image-spec/compare/v1.1.0...v1.1.1 Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 2 +- vendor.sum | 4 ++-- .../github.com/opencontainers/image-spec/specs-go/version.go | 2 +- vendor/modules.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vendor.mod b/vendor.mod index d784ed55b6b5..b721a73219d8 100644 --- a/vendor.mod +++ b/vendor.mod @@ -37,7 +37,7 @@ require ( github.com/moby/term v0.5.2 github.com/morikuni/aec v1.0.0 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.1.0 + github.com/opencontainers/image-spec v1.1.1 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 diff --git a/vendor.sum b/vendor.sum index 7dc3d017d722..5f58c88f589b 100644 --- a/vendor.sum +++ b/vendor.sum @@ -209,8 +209,8 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= diff --git a/vendor/github.com/opencontainers/image-spec/specs-go/version.go b/vendor/github.com/opencontainers/image-spec/specs-go/version.go index 7069ae44d715..c3897c7ca0c6 100644 --- a/vendor/github.com/opencontainers/image-spec/specs-go/version.go +++ b/vendor/github.com/opencontainers/image-spec/specs-go/version.go @@ -22,7 +22,7 @@ const ( // VersionMinor is for functionality in a backwards-compatible manner VersionMinor = 1 // VersionPatch is for backwards-compatible bug fixes - VersionPatch = 0 + VersionPatch = 1 // VersionDev indicates development branch. Releases will be empty string. VersionDev = "" diff --git a/vendor/modules.txt b/vendor/modules.txt index dae39cb3a8bc..d6abd3b510e2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -253,7 +253,7 @@ github.com/munnerz/goautoneg # github.com/opencontainers/go-digest v1.0.0 ## explicit; go 1.13 github.com/opencontainers/go-digest -# github.com/opencontainers/image-spec v1.1.0 +# github.com/opencontainers/image-spec v1.1.1 ## explicit; go 1.18 github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go/v1 From 5edc6748f40944f29ee03af5a54dd92e09973fa4 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 16 Jul 2025 08:43:27 +0200 Subject: [PATCH 003/193] cli/command: remove interactive login prompt from docker push/pull This patch removes the interactive prompts from `docker push/pull`. The prompt would only execute on a response status code 403 from the registry after trying the value set in `RegistryAuth`. Docker Hub could return 404 instead or 429, which would never execute the prompt. The UX regarding the prompt is also questionable since the user might not actually want to authenticate with a registry and the CLI could fail fast instead. The user can always run `docker login` or set the `DOCKER_AUTH_CONFIG` environment variable to get authenticated. Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 2b56b66b101f53436c90d594b6dc0d92faf38a28) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/push.go | 8 +------- cli/command/image/trust.go | 6 +----- cli/command/plugin/install.go | 11 +++-------- cli/command/plugin/upgrade.go | 2 +- cli/command/registry/search.go | 6 +----- cli/command/trust/sign.go | 6 +----- 6 files changed, 8 insertions(+), 31 deletions(-) diff --git a/cli/command/image/push.go b/cli/command/image/push.go index a875ac0d9f8d..88d8bec4fee4 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -74,8 +74,6 @@ Image index won't be pushed, meaning that other manifests, including attestation } // runPush performs a push against the engine based on the specified options. -// -//nolint:gocyclo // ignore cyclomatic complexity 17 of func `runPush` is high (> 16) for now. func runPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error { var platform *ocispec.Platform out := tui.NewOutput(dockerCli.Out()) @@ -115,14 +113,10 @@ To push the complete multi-platform image, remove the --platform flag. if err != nil { return err } - var requestPrivilege registrytypes.RequestAuthConfig - if dockerCli.In().IsTerminal() { - requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push") - } options := image.PushOptions{ All: opts.all, RegistryAuth: encodedAuth, - PrivilegeFunc: requestPrivilege, + PrivilegeFunc: nil, Platform: platform, } diff --git a/cli/command/image/trust.go b/cli/command/image/trust.go index e2980e847713..c44b51eb8f07 100644 --- a/cli/command/image/trust.go +++ b/cli/command/image/trust.go @@ -149,13 +149,9 @@ func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth tru if err != nil { return err } - var requestPrivilege registrytypes.RequestAuthConfig - if cli.In().IsTerminal() { - requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "pull") - } responseBody, err := cli.Client().ImagePull(ctx, reference.FamiliarString(imgRefAndAuth.Reference()), image.PullOptions{ RegistryAuth: encodedAuth, - PrivilegeFunc: requestPrivilege, + PrivilegeFunc: nil, All: opts.all, Platform: opts.platform, }) diff --git a/cli/command/plugin/install.go b/cli/command/plugin/install.go index 027478c0fc33..a0c053ff0673 100644 --- a/cli/command/plugin/install.go +++ b/cli/command/plugin/install.go @@ -56,7 +56,7 @@ func newInstallCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOptions, cmdName string) (types.PluginInstallOptions, error) { +func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOptions) (types.PluginInstallOptions, error) { // Names with both tag and digest will be treated by the daemon // as a pull by digest with a local name for the tag // (if no local name is provided). @@ -90,18 +90,13 @@ func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOpti return types.PluginInstallOptions{}, err } - var requestPrivilege registrytypes.RequestAuthConfig - if dockerCli.In().IsTerminal() { - requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, cmdName) - } - options := types.PluginInstallOptions{ RegistryAuth: encodedAuth, RemoteRef: remote, Disabled: opts.disable, AcceptAllPermissions: opts.grantPerms, AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.remote), - PrivilegeFunc: requestPrivilege, + PrivilegeFunc: nil, Args: opts.args, } return options, nil @@ -120,7 +115,7 @@ func runInstall(ctx context.Context, dockerCLI command.Cli, opts pluginOptions) localName = reference.FamiliarString(reference.TagNameOnly(aref)) } - options, err := buildPullConfig(ctx, dockerCLI, opts, "plugin install") + options, err := buildPullConfig(ctx, dockerCLI, opts) if err != nil { return err } diff --git a/cli/command/plugin/upgrade.go b/cli/command/plugin/upgrade.go index da9ea4076d84..5489232d9a2a 100644 --- a/cli/command/plugin/upgrade.go +++ b/cli/command/plugin/upgrade.go @@ -73,7 +73,7 @@ func runUpgrade(ctx context.Context, dockerCLI command.Cli, opts pluginOptions) } } - options, err := buildPullConfig(ctx, dockerCLI, opts, "plugin upgrade") + options, err := buildPullConfig(ctx, dockerCLI, opts) if err != nil { return err } diff --git a/cli/command/registry/search.go b/cli/command/registry/search.go index 5007322fc86b..01950acd0e69 100644 --- a/cli/command/registry/search.go +++ b/cli/command/registry/search.go @@ -63,13 +63,9 @@ func runSearch(ctx context.Context, dockerCli command.Cli, options searchOptions return err } - var requestPrivilege registrytypes.RequestAuthConfig - if dockerCli.In().IsTerminal() { - requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(dockerCli, indexInfo, "search") - } results, err := dockerCli.Client().ImageSearch(ctx, options.term, registrytypes.SearchOptions{ RegistryAuth: encodedAuth, - PrivilegeFunc: requestPrivilege, + PrivilegeFunc: nil, Filters: options.filter.Value(), Limit: options.limit, }) diff --git a/cli/command/trust/sign.go b/cli/command/trust/sign.go index 077620155aee..e03b9cac7fc6 100644 --- a/cli/command/trust/sign.go +++ b/cli/command/trust/sign.go @@ -82,10 +82,6 @@ func runSignImage(ctx context.Context, dockerCLI command.Cli, options signOption return trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), err) } } - var requestPrivilege registrytypes.RequestAuthConfig - if dockerCLI.In().IsTerminal() { - requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(dockerCLI, imgRefAndAuth.RepoInfo().Index, "push") - } target, err := createTarget(notaryRepo, imgRefAndAuth.Tag()) if err != nil || options.local { switch err := err.(type) { @@ -104,7 +100,7 @@ func runSignImage(ctx context.Context, dockerCLI command.Cli, options signOption } responseBody, err := dockerCLI.Client().ImagePush(ctx, reference.FamiliarString(imgRefAndAuth.Reference()), imagetypes.PushOptions{ RegistryAuth: encodedAuth, - PrivilegeFunc: requestPrivilege, + PrivilegeFunc: nil, }) if err != nil { return err From 5566c3a9b80fc1b03e0e95525fbfe2b80bddac96 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:01:35 +0200 Subject: [PATCH 004/193] cli/command: remove usages of RegistryAuthenticationPrivilegedFunc This patch deprecates the unused `RegistryAuthenticationPrivilegedFunc`. The function would prompt the user when the registry returns a 403 after trying the initial auth value set in `RegistryAuth`. Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 29263e865b2d309753759b26fcbe16d94a8f25e3) Signed-off-by: Sebastiaan van Stijn --- cli/command/registry.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/command/registry.go b/cli/command/registry.go index be49d85b6bb8..d02d88db2dfd 100644 --- a/cli/command/registry.go +++ b/cli/command/registry.go @@ -36,6 +36,8 @@ const authConfigKey = "https://index.docker.io/v1/" // RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info // for the given command to prompt the user for username and password. +// +// Deprecated: this function is no longer used and will be removed in the next release. func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig { configKey := getAuthConfigKey(index.Name) isDefaultRegistry := configKey == authConfigKey || index.Official From 88274f4805fe18f7802e737c03fc9dec78767bca Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 5 Aug 2025 21:11:47 +0200 Subject: [PATCH 005/193] cli/command: remove deprecated CopyToFile utility It was deprecated in 7cc6b8ebf4aa1754ac9309027f315b270ea47c34, which is part of v28.x Signed-off-by: Sebastiaan van Stijn (cherry picked from commit de543475180cca011872b174b333b9663e59f112) Signed-off-by: Sebastiaan van Stijn --- cli/command/utils.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/cli/command/utils.go b/cli/command/utils.go index ab64ef8fc120..f024ffd2789d 100644 --- a/cli/command/utils.go +++ b/cli/command/utils.go @@ -14,24 +14,10 @@ import ( "github.com/docker/cli/cli/streams" "github.com/docker/cli/internal/prompt" "github.com/docker/docker/api/types/filters" - "github.com/moby/sys/atomicwriter" "github.com/pkg/errors" "github.com/spf13/pflag" ) -// CopyToFile writes the content of the reader to the specified file -// -// Deprecated: use [atomicwriter.New]. -func CopyToFile(outfile string, r io.Reader) error { - writer, err := atomicwriter.New(outfile, 0o600) - if err != nil { - return err - } - defer writer.Close() - _, err = io.Copy(writer, r) - return err -} - const ErrPromptTerminated = prompt.ErrTerminated // DisableInputEcho disables input echo on the provided streams.In. From 5c1cee4630cdf7c71f7c22997303ef615fda7418 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 5 Aug 2025 21:15:53 +0200 Subject: [PATCH 006/193] cli/command: remove deprecated ConfigureAuth utility It was deprecated in 6e4818e7d6d006f14ebac4c06fbe6ed958237408, which is part of v28.x and backported to v27.x. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 22cc0e90aea26150835c04fd833ed7061dd0102c) Signed-off-by: Sebastiaan van Stijn --- cli/command/registry.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/cli/command/registry.go b/cli/command/registry.go index be49d85b6bb8..950f55615fea 100644 --- a/cli/command/registry.go +++ b/cli/command/registry.go @@ -97,23 +97,6 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve return registrytypes.AuthConfig(authconfig), nil } -// ConfigureAuth handles prompting of user's username and password if needed. -// -// Deprecated: use [PromptUserForCredentials] instead. -func ConfigureAuth(ctx context.Context, cli Cli, flUser, flPassword string, authConfig *registrytypes.AuthConfig, _ bool) error { - defaultUsername := authConfig.Username - serverAddress := authConfig.ServerAddress - - newAuthConfig, err := PromptUserForCredentials(ctx, cli, flUser, flPassword, defaultUsername, serverAddress) - if err != nil { - return err - } - - authConfig.Username = newAuthConfig.Username - authConfig.Password = newAuthConfig.Password - return nil -} - // PromptUserForCredentials handles the CLI prompt for the user to input // credentials. // If argUser is not empty, then the user is only prompted for their password. From 6d4ffec3fbff5baa1d1e06e32f2d67dd7c6b637a Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 24 Jul 2025 01:33:55 +0200 Subject: [PATCH 007/193] cli/registry/client: remove deprecated RepoNameForReference This was deprecated in 6f46cd2f4b7aa7e4c2017a2cb1748dea706b3388, which is part of v28.x, and no longer used, so we can remove it. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit a87bde00680988dacaa2f21cb2ab4e198881e681) Signed-off-by: Sebastiaan van Stijn --- cli/registry/client/endpoint.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cli/registry/client/endpoint.go b/cli/registry/client/endpoint.go index df0b190d56fa..8b5431e1f87e 100644 --- a/cli/registry/client/endpoint.go +++ b/cli/registry/client/endpoint.go @@ -105,13 +105,6 @@ func getHTTPTransport(authConfig registrytypes.AuthConfig, endpoint registry.API return transport.NewTransport(base, modifiers...), nil } -// RepoNameForReference returns the repository name from a reference. -// -// Deprecated: this function is no longer used and will be removed in the next release. -func RepoNameForReference(ref reference.Named) (string, error) { - return reference.Path(reference.TrimNamed(ref)), nil -} - type existingTokenHandler struct { token string } From 363f4c00315fbb4c521b3720d2ce431c240b4860 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 6 Aug 2025 23:53:56 +0200 Subject: [PATCH 008/193] cli/command/completion: remove deprecated ValidArgsFn This was deprecated in 9f19820f883f1091256090279a94874d2fecba9d, which is part of v28.x, and unlikely used externally. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 5052a399159c87f5ea7c10aadf047c0c1856039f) Signed-off-by: Sebastiaan van Stijn --- cli/command/completion/functions.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cli/command/completion/functions.go b/cli/command/completion/functions.go index 41ebebf642c7..b6387c127883 100644 --- a/cli/command/completion/functions.go +++ b/cli/command/completion/functions.go @@ -13,11 +13,6 @@ import ( "github.com/spf13/cobra" ) -// ValidArgsFn a function to be used by cobra command as `ValidArgsFunction` to offer command line completion. -// -// Deprecated: use [cobra.CompletionFunc]. -type ValidArgsFn = cobra.CompletionFunc - // APIClientProvider provides a method to get an [client.APIClient], initializing // it if needed. // From 7399781944e79972204d2f4f5c1fd07e15fc6eaf Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 5 Aug 2025 16:25:40 +0200 Subject: [PATCH 009/193] cli/connhelper: remove dependency on pkg/process This package will not be included in the api or client modules, and we're currently only using a single function of it, and only the unix implementation, so let's fork it for now (although the package may be moved to moby/sys). This removes the last dependency on github.com/docker/docker. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 2abcbf842f24cff71fe9318a7dffc55bf0adff75) Signed-off-by: Sebastiaan van Stijn --- .../commandconn/commandconn_unix_test.go | 58 ++++++++++--- .../docker/docker/pkg/process/doc.go | 3 - .../docker/docker/pkg/process/process_unix.go | 82 ------------------- .../docker/pkg/process/process_windows.go | 45 ---------- vendor/modules.txt | 1 - 5 files changed, 46 insertions(+), 143 deletions(-) delete mode 100644 vendor/github.com/docker/docker/pkg/process/doc.go delete mode 100644 vendor/github.com/docker/docker/pkg/process/process_unix.go delete mode 100644 vendor/github.com/docker/docker/pkg/process/process_windows.go diff --git a/cli/connhelper/commandconn/commandconn_unix_test.go b/cli/connhelper/commandconn/commandconn_unix_test.go index 6c1219f4386e..b68ebe7bf4bb 100644 --- a/cli/connhelper/commandconn/commandconn_unix_test.go +++ b/cli/connhelper/commandconn/commandconn_unix_test.go @@ -4,12 +4,17 @@ package commandconn import ( "context" + "errors" "io" "io/fs" + "os" + "path/filepath" + "runtime" + "strconv" + "syscall" "testing" "time" - "github.com/docker/docker/pkg/process" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) @@ -51,16 +56,16 @@ func TestCloseRunningCommand(t *testing.T) { c, err := New(ctx, "sh", "-c", "while true; do sleep 1; done") assert.NilError(t, err) cmdConn := c.(*commandConn) - assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid)) + assert.Check(t, processAlive(cmdConn.cmd.Process.Pid)) n, err := c.Write([]byte("hello")) assert.Check(t, is.Equal(len("hello"), n)) assert.NilError(t, err) - assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid)) + assert.Check(t, processAlive(cmdConn.cmd.Process.Pid)) err = cmdConn.Close() assert.NilError(t, err) - assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid)) + assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid)) done <- struct{}{} }() @@ -79,7 +84,7 @@ func TestCloseTwice(t *testing.T) { c, err := New(ctx, "sh", "-c", "echo hello; sleep 1; exit 0") assert.NilError(t, err) cmdConn := c.(*commandConn) - assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid)) + assert.Check(t, processAlive(cmdConn.cmd.Process.Pid)) b := make([]byte, 32) n, err := c.Read(b) @@ -88,11 +93,11 @@ func TestCloseTwice(t *testing.T) { err = cmdConn.Close() assert.NilError(t, err) - assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid)) + assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid)) err = cmdConn.Close() assert.NilError(t, err) - assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid)) + assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid)) done <- struct{}{} }() @@ -111,7 +116,7 @@ func TestEOFTimeout(t *testing.T) { c, err := New(ctx, "sh", "-c", "sleep 20") assert.NilError(t, err) cmdConn := c.(*commandConn) - assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid)) + assert.Check(t, processAlive(cmdConn.cmd.Process.Pid)) cmdConn.stdout = mockStdoutEOF{} @@ -148,7 +153,7 @@ func TestCloseWhileWriting(t *testing.T) { c, err := New(ctx, "sh", "-c", "while true; do sleep 1; done") assert.NilError(t, err) cmdConn := c.(*commandConn) - assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid)) + assert.Check(t, processAlive(cmdConn.cmd.Process.Pid)) writeErrC := make(chan error) go func() { @@ -164,7 +169,7 @@ func TestCloseWhileWriting(t *testing.T) { err = c.Close() assert.NilError(t, err) - assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid)) + assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid)) writeErr := <-writeErrC assert.ErrorContains(t, writeErr, "file already closed") @@ -176,7 +181,7 @@ func TestCloseWhileReading(t *testing.T) { c, err := New(ctx, "sh", "-c", "while true; do sleep 1; done") assert.NilError(t, err) cmdConn := c.(*commandConn) - assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid)) + assert.Check(t, processAlive(cmdConn.cmd.Process.Pid)) readErrC := make(chan error) go func() { @@ -193,8 +198,37 @@ func TestCloseWhileReading(t *testing.T) { err = cmdConn.Close() assert.NilError(t, err) - assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid)) + assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid)) readErr := <-readErrC assert.Check(t, is.ErrorIs(readErr, fs.ErrClosed)) } + +// processAlive returns true if a process with a given pid is running. It only considers +// positive PIDs; 0 (all processes in the current process group), -1 (all processes +// with a PID larger than 1), and negative (-n, all processes in process group +// "n") values for pid are never considered to be alive. +// +// It was forked from https://github.com/moby/moby/blob/v28.3.3/pkg/process/process_unix.go#L17-L42 +func processAlive(pid int) bool { + if pid < 1 { + return false + } + switch runtime.GOOS { + case "darwin": + // OS X does not have a proc filesystem. Use kill -0 pid to judge if the + // process exists. From KILL(2): https://www.freebsd.org/cgi/man.cgi?query=kill&sektion=2&manpath=OpenDarwin+7.2.1 + // + // Sig may be one of the signals specified in sigaction(2) or it may + // be 0, in which case error checking is performed but no signal is + // actually sent. This can be used to check the validity of pid. + err := syscall.Kill(pid, 0) + + // Either the PID was found (no error), or we get an EPERM, which means + // the PID exists, but we don't have permissions to signal it. + return err == nil || errors.Is(err, syscall.EPERM) + default: + _, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid))) + return err == nil + } +} diff --git a/vendor/github.com/docker/docker/pkg/process/doc.go b/vendor/github.com/docker/docker/pkg/process/doc.go deleted file mode 100644 index dae536d7dbb0..000000000000 --- a/vendor/github.com/docker/docker/pkg/process/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package process provides a set of basic functions to manage individual -// processes. -package process diff --git a/vendor/github.com/docker/docker/pkg/process/process_unix.go b/vendor/github.com/docker/docker/pkg/process/process_unix.go deleted file mode 100644 index 13298bbdccb7..000000000000 --- a/vendor/github.com/docker/docker/pkg/process/process_unix.go +++ /dev/null @@ -1,82 +0,0 @@ -//go:build !windows - -package process - -import ( - "bytes" - "errors" - "fmt" - "os" - "path/filepath" - "runtime" - "strconv" - - "golang.org/x/sys/unix" -) - -// Alive returns true if process with a given pid is running. It only considers -// positive PIDs; 0 (all processes in the current process group), -1 (all processes -// with a PID larger than 1), and negative (-n, all processes in process group -// "n") values for pid are never considered to be alive. -func Alive(pid int) bool { - if pid < 1 { - return false - } - switch runtime.GOOS { - case "darwin": - // OS X does not have a proc filesystem. Use kill -0 pid to judge if the - // process exists. From KILL(2): https://www.freebsd.org/cgi/man.cgi?query=kill&sektion=2&manpath=OpenDarwin+7.2.1 - // - // Sig may be one of the signals specified in sigaction(2) or it may - // be 0, in which case error checking is performed but no signal is - // actually sent. This can be used to check the validity of pid. - err := unix.Kill(pid, 0) - - // Either the PID was found (no error) or we get an EPERM, which means - // the PID exists, but we don't have permissions to signal it. - return err == nil || errors.Is(err, unix.EPERM) - default: - _, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid))) - return err == nil - } -} - -// Kill force-stops a process. It only considers positive PIDs; 0 (all processes -// in the current process group), -1 (all processes with a PID larger than 1), -// and negative (-n, all processes in process group "n") values for pid are -// ignored. Refer to [KILL(2)] for details. -// -// [KILL(2)]: https://man7.org/linux/man-pages/man2/kill.2.html -func Kill(pid int) error { - if pid < 1 { - return fmt.Errorf("invalid PID (%d): only positive PIDs are allowed", pid) - } - err := unix.Kill(pid, unix.SIGKILL) - if err != nil && !errors.Is(err, unix.ESRCH) { - return err - } - return nil -} - -// Zombie return true if process has a state with "Z". It only considers positive -// PIDs; 0 (all processes in the current process group), -1 (all processes with -// a PID larger than 1), and negative (-n, all processes in process group "n") -// values for pid are ignored. Refer to [PROC(5)] for details. -// -// [PROC(5)]: https://man7.org/linux/man-pages/man5/proc.5.html -func Zombie(pid int) (bool, error) { - if pid < 1 { - return false, nil - } - data, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid)) - if err != nil { - if os.IsNotExist(err) { - return false, nil - } - return false, err - } - if cols := bytes.SplitN(data, []byte(" "), 4); len(cols) >= 3 && string(cols[2]) == "Z" { - return true, nil - } - return false, nil -} diff --git a/vendor/github.com/docker/docker/pkg/process/process_windows.go b/vendor/github.com/docker/docker/pkg/process/process_windows.go deleted file mode 100644 index 2dd57e825452..000000000000 --- a/vendor/github.com/docker/docker/pkg/process/process_windows.go +++ /dev/null @@ -1,45 +0,0 @@ -package process - -import ( - "os" - - "golang.org/x/sys/windows" -) - -// Alive returns true if process with a given pid is running. -func Alive(pid int) bool { - h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid)) - if err != nil { - return false - } - var c uint32 - err = windows.GetExitCodeProcess(h, &c) - _ = windows.CloseHandle(h) - if err != nil { - // From the GetExitCodeProcess function (processthreadsapi.h) API docs: - // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess - // - // The GetExitCodeProcess function returns a valid error code defined by the - // application only after the thread terminates. Therefore, an application should - // not use STILL_ACTIVE (259) as an error code (STILL_ACTIVE is a macro for - // STATUS_PENDING (minwinbase.h)). If a thread returns STILL_ACTIVE (259) as - // an error code, then applications that test for that value could interpret it - // to mean that the thread is still running, and continue to test for the - // completion of the thread after the thread has terminated, which could put - // the application into an infinite loop. - return c == uint32(windows.STATUS_PENDING) - } - return true -} - -// Kill force-stops a process. -func Kill(pid int) error { - p, err := os.FindProcess(pid) - if err == nil { - err = p.Kill() - if err != nil && err != os.ErrProcessDone { - return err - } - } - return nil -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 037c93d3bec6..d80b643dc479 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -96,7 +96,6 @@ github.com/docker/docker/internal/lazyregexp github.com/docker/docker/internal/multierror github.com/docker/docker/pkg/homedir github.com/docker/docker/pkg/jsonmessage -github.com/docker/docker/pkg/process github.com/docker/docker/pkg/progress github.com/docker/docker/pkg/stdcopy github.com/docker/docker/pkg/streamformatter From 7d3bde083cb614022bb317f96db0457272af24ac Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 16 Jul 2025 15:12:37 +0200 Subject: [PATCH 010/193] cli/command/image: move build-context detection to build Removes direct imports of github.com/docker/docker/builder in the image package, to be moved later. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 260f1dbebb9f3d70d2bdda67e511a6bee9fe365b) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build.go | 68 +++++++++++------------ cli/command/image/build/context_detect.go | 39 +++++++++++++ 2 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 cli/command/image/build/context_detect.go diff --git a/cli/command/image/build.go b/cli/command/image/build.go index 66beeee2bba2..6c6db76675ea 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -28,7 +28,6 @@ import ( buildtypes "github.com/docker/docker/api/types/build" "github.com/docker/docker/api/types/container" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/builder/remotecontext/urlutil" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/streamformatter" "github.com/moby/go-archive" @@ -76,12 +75,6 @@ func (o buildOptions) dockerfileFromStdin() bool { return o.dockerfileName == "-" } -// contextFromStdin returns true when the user specified that the build context -// should be read from stdin -func (o buildOptions) contextFromStdin() bool { - return o.context == "-" -} - func newBuildOptions() buildOptions { ulimits := make(map[string]*container.Ulimit) return buildOptions{ @@ -189,21 +182,24 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) buildCtx io.ReadCloser dockerfileCtx io.ReadCloser contextDir string - tempDir string relDockerfile string progBuff io.Writer buildBuff io.Writer remote string ) + contextType, err := build.DetectContextType(options.context) + if err != nil { + return err + } + if options.dockerfileFromStdin() { - if options.contextFromStdin() { + if contextType == build.ContextTypeStdin { return errors.New("invalid argument: can't use stdin for both build context and dockerfile") } dockerfileCtx = dockerCli.In() } - specifiedContext := options.context progBuff = dockerCli.Out() buildBuff = dockerCli.Out() if options.quiet { @@ -217,13 +213,19 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) } } - switch { - case options.contextFromStdin(): + switch contextType { + case build.ContextTypeStdin: // buildCtx is tar archive. if stdin was dockerfile then it is wrapped buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName) - case isLocalDir(specifiedContext): - contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName) - if err == nil && strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) { + if err != nil { + return fmt.Errorf("unable to prepare context from STDIN: %w", err) + } + case build.ContextTypeLocal: + contextDir, relDockerfile, err = build.GetContextFromLocalDir(options.context, options.dockerfileName) + if err != nil { + return errors.Errorf("unable to prepare context: %s", err) + } + if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) { // Dockerfile is outside of build-context; read the Dockerfile and pass it as dockerfileCtx dockerfileCtx, err = os.Open(options.dockerfileName) if err != nil { @@ -231,24 +233,23 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) } defer dockerfileCtx.Close() } - case urlutil.IsGitURL(specifiedContext): - tempDir, relDockerfile, err = build.GetContextFromGitURL(specifiedContext, options.dockerfileName) - case urlutil.IsURL(specifiedContext): - buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, specifiedContext, options.dockerfileName) - default: - return errors.Errorf("unable to prepare context: path %q not found", specifiedContext) - } - - if err != nil { - if options.quiet && urlutil.IsURL(specifiedContext) { - _, _ = fmt.Fprintln(dockerCli.Err(), progBuff) + case build.ContextTypeGit: + var tempDir string + tempDir, relDockerfile, err = build.GetContextFromGitURL(options.context, options.dockerfileName) + if err != nil { + return errors.Errorf("unable to prepare context: %s", err) } - return errors.Errorf("unable to prepare context: %s", err) - } - - if tempDir != "" { - defer os.RemoveAll(tempDir) + defer func() { + _ = os.RemoveAll(tempDir) + }() contextDir = tempDir + case build.ContextTypeRemote: + buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, options.context, options.dockerfileName) + if err != nil && options.quiet { + _, _ = fmt.Fprintln(dockerCli.Err(), progBuff) + } + default: + return errors.Errorf("unable to prepare context: path %q not found", options.context) } // read from a directory into tar archive @@ -415,11 +416,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) return nil } -func isLocalDir(c string) bool { - _, err := os.Stat(c) - return err == nil -} - type translatorFunc func(context.Context, reference.NamedTagged) (reference.Canonical, error) // validateTag checks if the given image name can be resolved. diff --git a/cli/command/image/build/context_detect.go b/cli/command/image/build/context_detect.go new file mode 100644 index 000000000000..7e47addc8055 --- /dev/null +++ b/cli/command/image/build/context_detect.go @@ -0,0 +1,39 @@ +package build + +import ( + "fmt" + "os" + + "github.com/docker/docker/builder/remotecontext/urlutil" +) + +// ContextType describes the type (source) of build-context specified. +type ContextType string + +const ( + ContextTypeStdin ContextType = "stdin" // ContextTypeStdin indicates that the build-context is a TAR archive passed through STDIN. + ContextTypeLocal ContextType = "local" // ContextTypeLocal indicates that the build-context is a local directory. + ContextTypeRemote ContextType = "remote" // ContextTypeRemote indicates that the build-context is a remote URL. + ContextTypeGit ContextType = "git" // ContextTypeGit indicates that the build-context is a GIT URL. +) + +// DetectContextType detects the type (source) of the build-context. +func DetectContextType(specifiedContext string) (ContextType, error) { + switch { + case specifiedContext == "-": + return ContextTypeStdin, nil + case isLocalDir(specifiedContext): + return ContextTypeLocal, nil + case urlutil.IsGitURL(specifiedContext): + return ContextTypeGit, nil + case urlutil.IsURL(specifiedContext): + return ContextTypeRemote, nil + default: + return "", fmt.Errorf("unable to prepare context: path %q not found", specifiedContext) + } +} + +func isLocalDir(c string) bool { + _, err := os.Stat(c) + return err == nil +} From a4f8f22a33cb7ce0d8d62827d6b2ac71bd3bd58d Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 17 Jul 2025 01:34:55 +0200 Subject: [PATCH 011/193] Merge pull request #6190 from thaJeztah/fork_remotecontext add local fork of github.com/docker/docker/builder/remotecontext (cherry picked from commit 8c317ad3fd1fb2386edb3b6c0c155733b276ee9c) Signed-off-by: Sebastiaan van Stijn --- Dockerfile | 2 +- cli/command/image/build/context.go | 2 +- cli/command/image/build/context_detect.go | 2 +- .../image/build/internal}/git/gitutils.go | 3 + .../image/build/internal/git/gitutils_test.go | 382 ++++++++++++++++++ .../image/build/internal}/urlutil/urlutil.go | 2 +- .../build/internal/urlutil/urlutil_test.go | 42 ++ dockerfiles/Dockerfile.dev | 1 + vendor.mod | 2 +- vendor/modules.txt | 2 - 10 files changed, 433 insertions(+), 7 deletions(-) rename {vendor/github.com/docker/docker/builder/remotecontext => cli/command/image/build/internal}/git/gitutils.go (99%) create mode 100644 cli/command/image/build/internal/git/gitutils_test.go rename {vendor/github.com/docker/docker/builder/remotecontext => cli/command/image/build/internal}/urlutil/urlutil.go (98%) create mode 100644 cli/command/image/build/internal/urlutil/urlutil_test.go diff --git a/Dockerfile b/Dockerfile index 6ea150890011..6a697c1b543c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build-base-alpine ENV GOTOOLCHAIN=local COPY --link --from=xx / / -RUN apk add --no-cache bash clang lld llvm file git +RUN apk add --no-cache bash clang lld llvm file git git-daemon WORKDIR /go/src/github.com/docker/cli FROM build-base-alpine AS build-alpine diff --git a/cli/command/image/build/context.go b/cli/command/image/build/context.go index ca70d5484c77..add175c2c0f3 100644 --- a/cli/command/image/build/context.go +++ b/cli/command/image/build/context.go @@ -16,7 +16,7 @@ import ( "strings" "time" - "github.com/docker/docker/builder/remotecontext/git" + "github.com/docker/cli/cli/command/image/build/internal/git" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/streamformatter" "github.com/moby/go-archive" diff --git a/cli/command/image/build/context_detect.go b/cli/command/image/build/context_detect.go index 7e47addc8055..c5edc4ecf161 100644 --- a/cli/command/image/build/context_detect.go +++ b/cli/command/image/build/context_detect.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/docker/docker/builder/remotecontext/urlutil" + "github.com/docker/cli/cli/command/image/build/internal/urlutil" ) // ContextType describes the type (source) of build-context specified. diff --git a/vendor/github.com/docker/docker/builder/remotecontext/git/gitutils.go b/cli/command/image/build/internal/git/gitutils.go similarity index 99% rename from vendor/github.com/docker/docker/builder/remotecontext/git/gitutils.go rename to cli/command/image/build/internal/git/gitutils.go index b02993511fbb..1f3063b50433 100644 --- a/vendor/github.com/docker/docker/builder/remotecontext/git/gitutils.go +++ b/cli/command/image/build/internal/git/gitutils.go @@ -148,6 +148,9 @@ func supportsShallowClone(remoteURL string) bool { // Try a HEAD request and fallback to a Get request on error res, err := http.Head(serviceURL) // #nosec G107 + if err == nil { + _ = res.Body.Close() + } if err != nil || res.StatusCode != http.StatusOK { res, err = http.Get(serviceURL) // #nosec G107 if err == nil { diff --git a/cli/command/image/build/internal/git/gitutils_test.go b/cli/command/image/build/internal/git/gitutils_test.go new file mode 100644 index 000000000000..61dfb56d62f8 --- /dev/null +++ b/cli/command/image/build/internal/git/gitutils_test.go @@ -0,0 +1,382 @@ +package git + +import ( + "bytes" + "fmt" + "net/http" + "net/http/cgi" + "net/http/httptest" + "net/url" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestParseRemoteURL(t *testing.T) { + tests := []struct { + doc string + url string + expected gitRepo + }{ + { + doc: "git scheme uppercase, no url-fragment", + url: "GIT://github.com/user/repo.git", + expected: gitRepo{ + remote: "git://github.com/user/repo.git", + ref: "master", + }, + }, + { + doc: "git scheme, no url-fragment", + url: "git://github.com/user/repo.git", + expected: gitRepo{ + remote: "git://github.com/user/repo.git", + ref: "master", + }, + }, + { + doc: "git scheme, with url-fragment", + url: "git://github.com/user/repo.git#mybranch:mydir/mysubdir/", + expected: gitRepo{ + remote: "git://github.com/user/repo.git", + ref: "mybranch", + subdir: "mydir/mysubdir/", + }, + }, + { + doc: "https scheme, no url-fragment", + url: "https://github.com/user/repo.git", + expected: gitRepo{ + remote: "https://github.com/user/repo.git", + ref: "master", + }, + }, + { + doc: "https scheme, with url-fragment", + url: "https://github.com/user/repo.git#mybranch:mydir/mysubdir/", + expected: gitRepo{ + remote: "https://github.com/user/repo.git", + ref: "mybranch", + subdir: "mydir/mysubdir/", + }, + }, + { + doc: "git@, no url-fragment", + url: "git@github.com:user/repo.git", + expected: gitRepo{ + remote: "git@github.com:user/repo.git", + ref: "master", + }, + }, + { + doc: "git@, with url-fragment", + url: "git@github.com:user/repo.git#mybranch:mydir/mysubdir/", + expected: gitRepo{ + remote: "git@github.com:user/repo.git", + ref: "mybranch", + subdir: "mydir/mysubdir/", + }, + }, + { + doc: "ssh, no url-fragment", + url: "ssh://github.com/user/repo.git", + expected: gitRepo{ + remote: "ssh://github.com/user/repo.git", + ref: "master", + }, + }, + { + doc: "ssh, with url-fragment", + url: "ssh://github.com/user/repo.git#mybranch:mydir/mysubdir/", + expected: gitRepo{ + remote: "ssh://github.com/user/repo.git", + ref: "mybranch", + subdir: "mydir/mysubdir/", + }, + }, + { + doc: "ssh, with url-fragment and user", + url: "ssh://foo%40barcorp.com@github.com/user/repo.git#mybranch:mydir/mysubdir/", + expected: gitRepo{ + remote: "ssh://foo%40barcorp.com@github.com/user/repo.git", + ref: "mybranch", + subdir: "mydir/mysubdir/", + }, + }, + } + + for _, tc := range tests { + t.Run(tc.doc, func(t *testing.T) { + repo, err := parseRemoteURL(tc.url) + assert.NilError(t, err) + assert.Check(t, is.DeepEqual(tc.expected, repo, cmp.AllowUnexported(gitRepo{}))) + }) + } +} + +func TestCloneArgsSmartHttp(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + serverURL, err := url.Parse(server.URL) + assert.NilError(t, err) + + serverURL.Path = "/repo.git" + + mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query().Get("service") + w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-advertisement", q)) + }) + + args := fetchArgs(serverURL.String(), "master") + exp := []string{"fetch", "--depth", "1", "origin", "--", "master"} + assert.Check(t, is.DeepEqual(exp, args)) +} + +func TestCloneArgsDumbHttp(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + serverURL, _ := url.Parse(server.URL) + + serverURL.Path = "/repo.git" + + mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + }) + + args := fetchArgs(serverURL.String(), "master") + exp := []string{"fetch", "origin", "--", "master"} + assert.Check(t, is.DeepEqual(exp, args)) +} + +func TestCloneArgsGit(t *testing.T) { + args := fetchArgs("git://github.com/docker/docker", "master") + exp := []string{"fetch", "--depth", "1", "origin", "--", "master"} + assert.Check(t, is.DeepEqual(exp, args)) +} + +func gitGetConfig(name string) string { + b, err := gitRepo{}.gitWithinDir("", "config", "--get", name) + if err != nil { + // since we are interested in empty or non empty string, + // we can safely ignore the err here. + return "" + } + return strings.TrimSpace(string(b)) +} + +func TestCheckoutGit(t *testing.T) { + root := t.TempDir() + + gitpath, err := exec.LookPath("git") + assert.NilError(t, err) + gitversion, _ := exec.Command(gitpath, "version").CombinedOutput() + t.Logf("%s", gitversion) // E.g. "git version 2.30.2" + + // Serve all repositories under root using the Smart HTTP protocol so + // they can be cloned. The Dumb HTTP protocol is incompatible with + // shallow cloning but we unconditionally shallow-clone submodules, and + // we explicitly disable the file protocol. + // (Another option would be to use `git daemon` and the Git protocol, + // but that listens on a fixed port number which is a recipe for + // disaster in CI. Funnily enough, `git daemon --port=0` works but there + // is no easy way to discover which port got picked!) + + // Associate git-http-backend logs with the current (sub)test. + // Incompatible with parallel subtests. + currentSubtest := t + githttp := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var logs bytes.Buffer + (&cgi.Handler{ + Path: gitpath, + Args: []string{"http-backend"}, + Dir: root, + Env: []string{ + "GIT_PROJECT_ROOT=" + root, + "GIT_HTTP_EXPORT_ALL=1", + }, + Stderr: &logs, + }).ServeHTTP(w, r) + if logs.Len() == 0 { + return + } + for { + line, err := logs.ReadString('\n') + currentSubtest.Log("git-http-backend: " + line) + if err != nil { + break + } + } + }) + server := httptest.NewServer(&githttp) + defer server.Close() + + eol := "\n" + autocrlf := gitGetConfig("core.autocrlf") + switch autocrlf { + case "true": + eol = "\r\n" + case "false", "input", "": + // accepted values + default: + t.Logf(`unknown core.autocrlf value: "%s"`, autocrlf) + } + + must := func(out []byte, err error) { + t.Helper() + if len(out) > 0 { + t.Logf("%s", out) + } + assert.NilError(t, err) + } + + gitDir := filepath.Join(root, "repo") + must(gitRepo{}.gitWithinDir(root, "-c", "init.defaultBranch=master", "init", gitDir)) + must(gitRepo{}.gitWithinDir(gitDir, "config", "user.email", "test@docker.com")) + must(gitRepo{}.gitWithinDir(gitDir, "config", "user.name", "Docker test")) + assert.NilError(t, os.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM scratch"), 0o644)) + + subDir := filepath.Join(gitDir, "subdir") + assert.NilError(t, os.Mkdir(subDir, 0o755)) + assert.NilError(t, os.WriteFile(filepath.Join(subDir, "Dockerfile"), []byte("FROM scratch\nEXPOSE 5000"), 0o644)) + + if runtime.GOOS != "windows" { + assert.NilError(t, os.Symlink("../subdir", filepath.Join(gitDir, "parentlink"))) + assert.NilError(t, os.Symlink("/subdir", filepath.Join(gitDir, "absolutelink"))) + } + + must(gitRepo{}.gitWithinDir(gitDir, "add", "-A")) + must(gitRepo{}.gitWithinDir(gitDir, "commit", "-am", "First commit")) + must(gitRepo{}.gitWithinDir(gitDir, "checkout", "-b", "test")) + + assert.NilError(t, os.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM scratch\nEXPOSE 3000"), 0o644)) + assert.NilError(t, os.WriteFile(filepath.Join(subDir, "Dockerfile"), []byte("FROM busybox\nEXPOSE 5000"), 0o644)) + + must(gitRepo{}.gitWithinDir(gitDir, "add", "-A")) + must(gitRepo{}.gitWithinDir(gitDir, "commit", "-am", "Branch commit")) + must(gitRepo{}.gitWithinDir(gitDir, "checkout", "master")) + + // set up submodule + subrepoDir := filepath.Join(root, "subrepo") + must(gitRepo{}.gitWithinDir(root, "-c", "init.defaultBranch=master", "init", subrepoDir)) + must(gitRepo{}.gitWithinDir(subrepoDir, "config", "user.email", "test@docker.com")) + must(gitRepo{}.gitWithinDir(subrepoDir, "config", "user.name", "Docker test")) + + assert.NilError(t, os.WriteFile(filepath.Join(subrepoDir, "subfile"), []byte("subcontents"), 0o644)) + + must(gitRepo{}.gitWithinDir(subrepoDir, "add", "-A")) + must(gitRepo{}.gitWithinDir(subrepoDir, "commit", "-am", "Subrepo initial")) + + must(gitRepo{}.gitWithinDir(gitDir, "submodule", "add", server.URL+"/subrepo", "sub")) + must(gitRepo{}.gitWithinDir(gitDir, "add", "-A")) + must(gitRepo{}.gitWithinDir(gitDir, "commit", "-am", "With submodule")) + + type singleCase struct { + frag string + exp string + fail bool + submodule bool + } + + cases := []singleCase{ + {"", "FROM scratch", false, true}, + {"master", "FROM scratch", false, true}, + {":subdir", "FROM scratch" + eol + "EXPOSE 5000", false, false}, + {":nosubdir", "", true, false}, // missing directory error + {":Dockerfile", "", true, false}, // not a directory error + {"master:nosubdir", "", true, false}, + {"master:subdir", "FROM scratch" + eol + "EXPOSE 5000", false, false}, + {"master:../subdir", "", true, false}, + {"test", "FROM scratch" + eol + "EXPOSE 3000", false, false}, + {"test:", "FROM scratch" + eol + "EXPOSE 3000", false, false}, + {"test:subdir", "FROM busybox" + eol + "EXPOSE 5000", false, false}, + } + + if runtime.GOOS != "windows" { + // Windows GIT (2.7.1 x64) does not support parentlink/absolutelink. Sample output below + // git --work-tree .\repo --git-dir .\repo\.git add -A + // error: readlink("absolutelink"): Function not implemented + // error: unable to index file absolutelink + // fatal: adding files failed + cases = append(cases, singleCase{frag: "master:absolutelink", exp: "FROM scratch" + eol + "EXPOSE 5000", fail: false}) + cases = append(cases, singleCase{frag: "master:parentlink", exp: "FROM scratch" + eol + "EXPOSE 5000", fail: false}) + } + + for _, c := range cases { + t.Run(c.frag, func(t *testing.T) { + currentSubtest = t + ref, subdir := getRefAndSubdir(c.frag) + r, err := gitRepo{remote: server.URL + "/repo", ref: ref, subdir: subdir}.clone() + + if c.fail { + assert.Check(t, is.ErrorContains(err, "")) + return + } + assert.NilError(t, err) + defer os.RemoveAll(r) + if c.submodule { + b, err := os.ReadFile(filepath.Join(r, "sub/subfile")) + assert.NilError(t, err) + assert.Check(t, is.Equal("subcontents", string(b))) + } else { + _, err := os.Stat(filepath.Join(r, "sub/subfile")) + assert.ErrorContains(t, err, "") + assert.Assert(t, os.IsNotExist(err)) + } + + b, err := os.ReadFile(filepath.Join(r, "Dockerfile")) + assert.NilError(t, err) + assert.Check(t, is.Equal(c.exp, string(b))) + }) + } +} + +func TestValidGitTransport(t *testing.T) { + gitUrls := []string{ + "git://github.com/docker/docker", + "git@github.com:docker/docker.git", + "git@bitbucket.org:atlassianlabs/atlassian-docker.git", + "https://github.com/docker/docker.git", + "http://github.com/docker/docker.git", + "http://github.com/docker/docker.git#branch", + "http://github.com/docker/docker.git#:dir", + } + incompleteGitUrls := []string{ + "github.com/docker/docker", + } + + for _, u := range gitUrls { + if !isGitTransport(u) { + t.Fatalf("%q should be detected as valid Git prefix", u) + } + } + + for _, u := range incompleteGitUrls { + if isGitTransport(u) { + t.Fatalf("%q should not be detected as valid Git prefix", u) + } + } +} + +func TestGitInvalidRef(t *testing.T) { + gitUrls := []string{ + "git://github.com/moby/moby#--foo bar", + "git@github.com/moby/moby#--upload-pack=sleep;:", + "git@g.com:a/b.git#-B", + "git@g.com:a/b.git#with space", + } + + for _, u := range gitUrls { + _, err := Clone(u) + assert.Assert(t, err != nil) + // On Windows, git has different case for the "invalid refspec" error, + // so we can't use ErrorContains. + assert.Check(t, is.Contains(strings.ToLower(err.Error()), "invalid refspec")) + } +} diff --git a/vendor/github.com/docker/docker/builder/remotecontext/urlutil/urlutil.go b/cli/command/image/build/internal/urlutil/urlutil.go similarity index 98% rename from vendor/github.com/docker/docker/builder/remotecontext/urlutil/urlutil.go rename to cli/command/image/build/internal/urlutil/urlutil.go index a2225b5960e1..b1cf47ce316b 100644 --- a/vendor/github.com/docker/docker/builder/remotecontext/urlutil/urlutil.go +++ b/cli/command/image/build/internal/urlutil/urlutil.go @@ -8,7 +8,7 @@ package urlutil import ( "strings" - "github.com/docker/docker/internal/lazyregexp" + "github.com/docker/cli/internal/lazyregexp" ) // urlPathWithFragmentSuffix matches fragments to use as Git reference and build diff --git a/cli/command/image/build/internal/urlutil/urlutil_test.go b/cli/command/image/build/internal/urlutil/urlutil_test.go new file mode 100644 index 000000000000..f6d0a35de3ec --- /dev/null +++ b/cli/command/image/build/internal/urlutil/urlutil_test.go @@ -0,0 +1,42 @@ +package urlutil + +import "testing" + +var ( + gitUrls = []string{ + "git://github.com/docker/docker", + "git@github.com:docker/docker.git", + "git@bitbucket.org:atlassianlabs/atlassian-docker.git", + "https://github.com/docker/docker.git", + "http://github.com/docker/docker.git", + "http://github.com/docker/docker.git#branch", + "http://github.com/docker/docker.git#:dir", + } + incompleteGitUrls = []string{ + "github.com/docker/docker", + } + invalidGitUrls = []string{ + "http://github.com/docker/docker.git:#branch", + "https://github.com/docker/dgit", + } +) + +func TestIsGIT(t *testing.T) { + for _, url := range gitUrls { + if !IsGitURL(url) { + t.Fatalf("%q should be detected as valid Git url", url) + } + } + + for _, url := range incompleteGitUrls { + if !IsGitURL(url) { + t.Fatalf("%q should be detected as valid Git url", url) + } + } + + for _, url := range invalidGitUrls { + if IsGitURL(url) { + t.Fatalf("%q should not be detected as valid Git prefix", url) + } + } +} diff --git a/dockerfiles/Dockerfile.dev b/dockerfiles/Dockerfile.dev index 0b81e090c8b9..385cead812ce 100644 --- a/dockerfiles/Dockerfile.dev +++ b/dockerfiles/Dockerfile.dev @@ -50,6 +50,7 @@ RUN apk add --no-cache \ coreutils \ curl \ git \ + git-daemon \ jq \ nano diff --git a/vendor.mod b/vendor.mod index b721a73219d8..f971da50d4c6 100644 --- a/vendor.mod +++ b/vendor.mod @@ -34,6 +34,7 @@ require ( github.com/moby/sys/capability v0.4.0 github.com/moby/sys/sequential v0.6.0 github.com/moby/sys/signal v0.7.1 + github.com/moby/sys/symlink v0.3.0 github.com/moby/term v0.5.2 github.com/morikuni/aec v1.0.0 github.com/opencontainers/go-digest v1.0.0 @@ -83,7 +84,6 @@ require ( github.com/klauspost/compress v1.18.0 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/sys/symlink v0.3.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect diff --git a/vendor/modules.txt b/vendor/modules.txt index d80b643dc479..cebbc521b4f5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -89,8 +89,6 @@ github.com/docker/docker/api/types/system github.com/docker/docker/api/types/time github.com/docker/docker/api/types/versions github.com/docker/docker/api/types/volume -github.com/docker/docker/builder/remotecontext/git -github.com/docker/docker/builder/remotecontext/urlutil github.com/docker/docker/client github.com/docker/docker/internal/lazyregexp github.com/docker/docker/internal/multierror From 93a51c39f4e9e86a52e30667c593ba6fc6502653 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 22 Jul 2025 22:07:51 +0200 Subject: [PATCH 012/193] cli/command/registry: remove uses of registry.ParseSearchIndexInfo This utility was only used in the CLI, but the implementation was based on it being used on the daemon side, so included resolving the host's IP-address, mirrors, etc. The only reason it's used in the CLI is to provide credentials for the registry that's being searched, so reduce it to just that. There's more cleaning up to do in this area, so to make our lives easier, it's implemented locally as non-exported functions; likely to be replaced with something else. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit e504faf6dae1414ff281686d7830848b31eb86c2) Signed-off-by: Sebastiaan van Stijn --- cli/command/registry/search.go | 44 +++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/cli/command/registry/search.go b/cli/command/registry/search.go index 01950acd0e69..178a400ddb22 100644 --- a/cli/command/registry/search.go +++ b/cli/command/registry/search.go @@ -3,13 +3,13 @@ package registry import ( "context" "fmt" + "strings" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/opts" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/registry" "github.com/spf13/cobra" ) @@ -52,13 +52,7 @@ func runSearch(ctx context.Context, dockerCli command.Cli, options searchOptions if options.filter.Value().Contains("is-automated") { _, _ = fmt.Fprintln(dockerCli.Err(), `WARNING: the "is-automated" filter is deprecated, and searching for "is-automated=true" will not yield any results in future.`) } - indexInfo, err := registry.ParseSearchIndexInfo(options.term) - if err != nil { - return err - } - - authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), indexInfo) - encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig) + encodedAuth, err := getAuth(dockerCli, options.term) if err != nil { return err } @@ -80,3 +74,37 @@ func runSearch(ctx context.Context, dockerCli command.Cli, options searchOptions } return SearchWrite(searchCtx, results) } + +// authConfigKey is the key used to store credentials for Docker Hub. It is +// a copy of [registry.IndexServer]. +// +// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker/registry#IndexServer +const authConfigKey = "https://index.docker.io/v1/" + +// getAuth will use fetch auth based on the given search-term. If the search +// does not contain a hostname for the registry, it assumes Docker Hub is used, +// and resolves authentication for Docker Hub, otherwise it resolves authentication +// for the given registry. +func getAuth(dockerCLI command.Cli, reposName string) (encodedAuth string, err error) { + authCfgKey := splitReposSearchTerm(reposName) + if authCfgKey == "docker.io" || authCfgKey == "index.docker.io" { + authCfgKey = authConfigKey + } + + // Ignoring errors here, which was the existing behavior (likely + // "no credentials found"). We'll get an error when search failed, + // so fine to ignore in most situations. + authConfig, _ := dockerCLI.ConfigFile().GetAuthConfig(authCfgKey) + return registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig)) +} + +// splitReposSearchTerm breaks a search term into an index name and remote name +func splitReposSearchTerm(reposName string) string { + nameParts := strings.SplitN(reposName, "/", 2) + if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") { + // This is a Docker Hub repository (ex: samalba/hipache or ubuntu), + // use the default Docker Hub registry (docker.io) + return "docker.io" + } + return nameParts[0] +} From 8b9baffdf7ab5dc7a75e20b01e331d14efbdc133 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 24 Jul 2025 01:06:19 +0200 Subject: [PATCH 013/193] add internal fork of docker/docker/registry This adds an internal fork of [github.com/docker/docker/registry], taken at commit [moby@f651a5d]. Git history was not preserved in this fork, but can be found using the URLs provided. This fork was created to remove the dependency on the "Moby" codebase, and because the CLI only needs a subset of its features. The original package was written specifically for use in the daemon code, and includes functionality that cannot be used in the CLI. [github.com/docker/docker/registry]: https://pkg.go.dev/github.com/docker/docker@v28.3.2+incompatible/registry [moby@49306c6]: https://github.com/moby/moby/tree/49306c607b72c5bf0a8e426f5a9760fa5ef96ea0/registry Signed-off-by: Sebastiaan van Stijn (cherry picked from commit f6b90bc2535042dfcdbca4b3167e2533f78a51a0) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/push.go | 2 +- cli/command/image/trust.go | 2 +- cli/command/plugin/install.go | 2 +- cli/command/plugin/push.go | 2 +- cli/command/registry/login.go | 2 +- cli/command/registry/login_test.go | 2 +- cli/command/registry/logout.go | 2 +- cli/command/service/trust.go | 2 +- cli/command/system/info.go | 2 +- cli/registry/client/endpoint.go | 2 +- cli/registry/client/fetcher.go | 2 +- cli/trust/trust.go | 2 +- cli/trust/trust_push.go | 2 +- internal/oauth/manager/manager.go | 2 +- .../docker => internal}/registry/auth.go | 25 +- internal/registry/auth_test.go | 106 +++ .../docker => internal}/registry/config.go | 118 ++-- internal/registry/config_test.go | 340 ++++++++++ internal/registry/doc.go | 12 + .../docker => internal}/registry/errors.go | 14 +- .../docker => internal}/registry/registry.go | 19 +- internal/registry/registry_mock_test.go | 120 ++++ internal/registry/registry_test.go | 637 ++++++++++++++++++ .../docker => internal}/registry/search.go | 0 .../registry/search_endpoint_v1.go | 12 +- internal/registry/search_endpoint_v1_test.go | 237 +++++++ .../registry/search_session.go | 5 +- internal/registry/search_test.go | 418 ++++++++++++ .../docker => internal}/registry/service.go | 20 +- .../registry/service_v2.go | 1 - internal/registry/types.go | 13 + vendor.mod | 4 +- .../docker/docker/pkg/homedir/homedir.go | 28 - .../docker/pkg/homedir/homedir_linux.go | 105 --- .../docker/pkg/homedir/homedir_others.go | 32 - .../docker/docker/registry/config_unix.go | 16 - .../docker/docker/registry/config_windows.go | 20 - .../docker/docker/registry/types.go | 24 - vendor/modules.txt | 2 - 39 files changed, 1986 insertions(+), 370 deletions(-) rename {vendor/github.com/docker/docker => internal}/registry/auth.go (90%) create mode 100644 internal/registry/auth_test.go rename {vendor/github.com/docker/docker => internal}/registry/config.go (79%) create mode 100644 internal/registry/config_test.go create mode 100644 internal/registry/doc.go rename {vendor/github.com/docker/docker => internal}/registry/errors.go (85%) rename {vendor/github.com/docker/docker => internal}/registry/registry.go (91%) create mode 100644 internal/registry/registry_mock_test.go create mode 100644 internal/registry/registry_test.go rename {vendor/github.com/docker/docker => internal}/registry/search.go (100%) rename {vendor/github.com/docker/docker => internal}/registry/search_endpoint_v1.go (92%) create mode 100644 internal/registry/search_endpoint_v1_test.go rename {vendor/github.com/docker/docker => internal}/registry/search_session.go (98%) create mode 100644 internal/registry/search_test.go rename {vendor/github.com/docker/docker => internal}/registry/service.go (82%) rename {vendor/github.com/docker/docker => internal}/registry/service_v2.go (98%) create mode 100644 internal/registry/types.go delete mode 100644 vendor/github.com/docker/docker/pkg/homedir/homedir.go delete mode 100644 vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go delete mode 100644 vendor/github.com/docker/docker/pkg/homedir/homedir_others.go delete mode 100644 vendor/github.com/docker/docker/registry/config_unix.go delete mode 100644 vendor/github.com/docker/docker/registry/config_windows.go delete mode 100644 vendor/github.com/docker/docker/registry/types.go diff --git a/cli/command/image/push.go b/cli/command/image/push.go index 88d8bec4fee4..ff2bb39b91ed 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -16,11 +16,11 @@ import ( "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/streams" "github.com/docker/cli/internal/jsonstream" + "github.com/docker/cli/internal/registry" "github.com/docker/cli/internal/tui" "github.com/docker/docker/api/types/auxprogress" "github.com/docker/docker/api/types/image" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/registry" "github.com/morikuni/aec" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" diff --git a/cli/command/image/trust.go b/cli/command/image/trust.go index c44b51eb8f07..e0288b9cba98 100644 --- a/cli/command/image/trust.go +++ b/cli/command/image/trust.go @@ -11,9 +11,9 @@ import ( "github.com/docker/cli/cli/streams" "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/jsonstream" + "github.com/docker/cli/internal/registry" "github.com/docker/docker/api/types/image" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/registry" "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" diff --git a/cli/command/plugin/install.go b/cli/command/plugin/install.go index a0c053ff0673..a737f8d9c25e 100644 --- a/cli/command/plugin/install.go +++ b/cli/command/plugin/install.go @@ -11,9 +11,9 @@ import ( "github.com/docker/cli/cli/command/image" "github.com/docker/cli/internal/jsonstream" "github.com/docker/cli/internal/prompt" + "github.com/docker/cli/internal/registry" "github.com/docker/docker/api/types" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/registry" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" diff --git a/cli/command/plugin/push.go b/cli/command/plugin/push.go index 402068034797..30e9c7f5d1c2 100644 --- a/cli/command/plugin/push.go +++ b/cli/command/plugin/push.go @@ -8,8 +8,8 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/jsonstream" + "github.com/docker/cli/internal/registry" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/registry" "github.com/pkg/errors" "github.com/spf13/cobra" ) diff --git a/cli/command/registry/login.go b/cli/command/registry/login.go index 3679e51eddd5..54c3e462d643 100644 --- a/cli/command/registry/login.go +++ b/cli/command/registry/login.go @@ -15,10 +15,10 @@ import ( "github.com/docker/cli/cli/config/configfile" configtypes "github.com/docker/cli/cli/config/types" "github.com/docker/cli/internal/oauth/manager" + "github.com/docker/cli/internal/registry" "github.com/docker/cli/internal/tui" registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/client" - "github.com/docker/docker/registry" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" diff --git a/cli/command/registry/login_test.go b/cli/command/registry/login_test.go index 58ca73c6a6cf..d112f08e88e3 100644 --- a/cli/command/registry/login_test.go +++ b/cli/command/registry/login_test.go @@ -13,11 +13,11 @@ import ( configtypes "github.com/docker/cli/cli/config/types" "github.com/docker/cli/cli/streams" "github.com/docker/cli/internal/prompt" + "github.com/docker/cli/internal/registry" "github.com/docker/cli/internal/test" registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/system" "github.com/docker/docker/client" - "github.com/docker/docker/registry" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/fs" diff --git a/cli/command/registry/logout.go b/cli/command/registry/logout.go index 2af2cdad3f3f..34498871a557 100644 --- a/cli/command/registry/logout.go +++ b/cli/command/registry/logout.go @@ -8,7 +8,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config/credentials" "github.com/docker/cli/internal/oauth/manager" - "github.com/docker/docker/registry" + "github.com/docker/cli/internal/registry" "github.com/spf13/cobra" ) diff --git a/cli/command/service/trust.go b/cli/command/service/trust.go index 6fb4d129a5ff..49c65d668d71 100644 --- a/cli/command/service/trust.go +++ b/cli/command/service/trust.go @@ -6,8 +6,8 @@ import ( "github.com/distribution/reference" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/trust" + "github.com/docker/cli/internal/registry" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/registry" "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" diff --git a/cli/command/system/info.go b/cli/command/system/info.go index 7f82e4e5d85f..c892961402d8 100644 --- a/cli/command/system/info.go +++ b/cli/command/system/info.go @@ -19,11 +19,11 @@ import ( "github.com/docker/cli/cli/debug" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/internal/lazyregexp" + "github.com/docker/cli/internal/registry" "github.com/docker/cli/templates" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/system" "github.com/docker/docker/client" - "github.com/docker/docker/registry" "github.com/docker/go-units" "github.com/spf13/cobra" ) diff --git a/cli/registry/client/endpoint.go b/cli/registry/client/endpoint.go index 8b5431e1f87e..1b966e330dbb 100644 --- a/cli/registry/client/endpoint.go +++ b/cli/registry/client/endpoint.go @@ -6,10 +6,10 @@ import ( "time" "github.com/distribution/reference" + "github.com/docker/cli/internal/registry" "github.com/docker/distribution/registry/client/auth" "github.com/docker/distribution/registry/client/transport" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/registry" "github.com/pkg/errors" ) diff --git a/cli/registry/client/fetcher.go b/cli/registry/client/fetcher.go index f270d494324f..e169179d1670 100644 --- a/cli/registry/client/fetcher.go +++ b/cli/registry/client/fetcher.go @@ -6,6 +6,7 @@ import ( "github.com/distribution/reference" "github.com/docker/cli/cli/manifest/types" + "github.com/docker/cli/internal/registry" "github.com/docker/distribution" "github.com/docker/distribution/manifest/manifestlist" "github.com/docker/distribution/manifest/ocischema" @@ -13,7 +14,6 @@ import ( "github.com/docker/distribution/registry/api/errcode" v2 "github.com/docker/distribution/registry/api/v2" distclient "github.com/docker/distribution/registry/client" - "github.com/docker/docker/registry" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" diff --git a/cli/trust/trust.go b/cli/trust/trust.go index 27453ae22ee4..abfc8fdb5f48 100644 --- a/cli/trust/trust.go +++ b/cli/trust/trust.go @@ -14,11 +14,11 @@ import ( "github.com/distribution/reference" "github.com/docker/cli/cli/config" + "github.com/docker/cli/internal/registry" "github.com/docker/distribution/registry/client/auth" "github.com/docker/distribution/registry/client/auth/challenge" "github.com/docker/distribution/registry/client/transport" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/registry" "github.com/docker/go-connections/tlsconfig" "github.com/opencontainers/go-digest" "github.com/pkg/errors" diff --git a/cli/trust/trust_push.go b/cli/trust/trust_push.go index 1a8c5e4b7281..047990c4538e 100644 --- a/cli/trust/trust_push.go +++ b/cli/trust/trust_push.go @@ -11,9 +11,9 @@ import ( "github.com/distribution/reference" "github.com/docker/cli/cli/streams" "github.com/docker/cli/internal/jsonstream" + "github.com/docker/cli/internal/registry" "github.com/docker/docker/api/types" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/registry" "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/theupdateframework/notary/client" diff --git a/internal/oauth/manager/manager.go b/internal/oauth/manager/manager.go index 115006436404..96deb61b1325 100644 --- a/internal/oauth/manager/manager.go +++ b/internal/oauth/manager/manager.go @@ -14,8 +14,8 @@ import ( "github.com/docker/cli/cli/streams" "github.com/docker/cli/internal/oauth" "github.com/docker/cli/internal/oauth/api" + "github.com/docker/cli/internal/registry" "github.com/docker/cli/internal/tui" - "github.com/docker/docker/registry" "github.com/morikuni/aec" "github.com/sirupsen/logrus" diff --git a/vendor/github.com/docker/docker/registry/auth.go b/internal/registry/auth.go similarity index 90% rename from vendor/github.com/docker/docker/registry/auth.go rename to internal/registry/auth.go index 1b0eeeed0b1c..f99c58a12141 100644 --- a/vendor/github.com/docker/docker/registry/auth.go +++ b/internal/registry/auth.go @@ -40,9 +40,9 @@ type staticCredentialStore struct { // NewStaticCredentialStore returns a credential store // which always returns the same credential values. -func NewStaticCredentialStore(auth *registry.AuthConfig) auth.CredentialStore { +func NewStaticCredentialStore(ac *registry.AuthConfig) auth.CredentialStore { return staticCredentialStore{ - auth: auth, + auth: ac, } } @@ -60,7 +60,7 @@ func (scs staticCredentialStore) RefreshToken(*url.URL, string) string { return scs.auth.IdentityToken } -func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) { +func (staticCredentialStore) SetRefreshToken(*url.URL, string, string) { } // loginV2 tries to login to the v2 registry server. The given registry @@ -131,12 +131,15 @@ func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifi // to just its hostname. It is used to match credentials, which may be either // stored as hostname or as hostname including scheme (in legacy configuration // files). -func ConvertToHostname(url string) string { - stripped := url - if strings.HasPrefix(stripped, "http://") { - stripped = strings.TrimPrefix(stripped, "http://") - } else if strings.HasPrefix(stripped, "https://") { - stripped = strings.TrimPrefix(stripped, "https://") +func ConvertToHostname(maybeURL string) string { + stripped := maybeURL + if scheme, remainder, ok := strings.Cut(stripped, "://"); ok { + switch scheme { + case "http", "https": + stripped = remainder + default: + // unknown, or no scheme; doing nothing for now, as we never did. + } } stripped, _, _ = strings.Cut(stripped, "/") return stripped @@ -175,9 +178,9 @@ func (err PingResponseError) Error() string { // PingV2Registry attempts to ping a v2 registry and on success return a // challenge manager for the supported authentication types. // If a response is received but cannot be interpreted, a PingResponseError will be returned. -func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.Manager, error) { +func PingV2Registry(endpoint *url.URL, authTransport http.RoundTripper) (challenge.Manager, error) { pingClient := &http.Client{ - Transport: transport, + Transport: authTransport, Timeout: 15 * time.Second, } endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/" diff --git a/internal/registry/auth_test.go b/internal/registry/auth_test.go new file mode 100644 index 000000000000..927254177d3b --- /dev/null +++ b/internal/registry/auth_test.go @@ -0,0 +1,106 @@ +package registry + +import ( + "testing" + + "github.com/docker/docker/api/types/registry" + "gotest.tools/v3/assert" +) + +func buildAuthConfigs() map[string]registry.AuthConfig { + authConfigs := map[string]registry.AuthConfig{} + + for _, reg := range []string{"testIndex", IndexServer} { + authConfigs[reg] = registry.AuthConfig{ + Username: "docker-user", + Password: "docker-pass", + } + } + + return authConfigs +} + +func TestResolveAuthConfigIndexServer(t *testing.T) { + authConfigs := buildAuthConfigs() + indexConfig := authConfigs[IndexServer] + + officialIndex := ®istry.IndexInfo{ + Official: true, + } + privateIndex := ®istry.IndexInfo{ + Official: false, + } + + resolved := ResolveAuthConfig(authConfigs, officialIndex) + assert.Equal(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServer") + + resolved = ResolveAuthConfig(authConfigs, privateIndex) + assert.Check(t, resolved != indexConfig, "Expected ResolveAuthConfig to not return IndexServer") +} + +func TestResolveAuthConfigFullURL(t *testing.T) { + authConfigs := buildAuthConfigs() + + registryAuth := registry.AuthConfig{ + Username: "foo-user", + Password: "foo-pass", + } + localAuth := registry.AuthConfig{ + Username: "bar-user", + Password: "bar-pass", + } + officialAuth := registry.AuthConfig{ + Username: "baz-user", + Password: "baz-pass", + } + authConfigs[IndexServer] = officialAuth + + expectedAuths := map[string]registry.AuthConfig{ + "registry.example.com": registryAuth, + "localhost:8000": localAuth, + "example.com": localAuth, + } + + validRegistries := map[string][]string{ + "registry.example.com": { + "https://registry.example.com/v1/", + "http://registry.example.com/v1/", + "registry.example.com", + "registry.example.com/v1/", + }, + "localhost:8000": { + "https://localhost:8000/v1/", + "http://localhost:8000/v1/", + "localhost:8000", + "localhost:8000/v1/", + }, + "example.com": { + "https://example.com/v1/", + "http://example.com/v1/", + "example.com", + "example.com/v1/", + }, + } + + for configKey, registries := range validRegistries { + configured, ok := expectedAuths[configKey] + if !ok { + t.Fail() + } + index := ®istry.IndexInfo{ + Name: configKey, + } + for _, reg := range registries { + authConfigs[reg] = configured + resolved := ResolveAuthConfig(authConfigs, index) + if resolved.Username != configured.Username || resolved.Password != configured.Password { + t.Errorf("%s -> %v != %v\n", reg, resolved, configured) + } + delete(authConfigs, reg) + resolved = ResolveAuthConfig(authConfigs, index) + if resolved.Username == configured.Username || resolved.Password == configured.Password { + t.Errorf("%s -> %v == %v\n", reg, resolved, configured) + } + } + } +} diff --git a/vendor/github.com/docker/docker/registry/config.go b/internal/registry/config.go similarity index 79% rename from vendor/github.com/docker/docker/registry/config.go rename to internal/registry/config.go index 218a12683a63..1aa466147cd0 100644 --- a/vendor/github.com/docker/docker/registry/config.go +++ b/internal/registry/config.go @@ -1,3 +1,6 @@ +// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: +//go:build go1.23 + package registry import ( @@ -6,6 +9,8 @@ import ( "net/url" "os" "path/filepath" + "regexp" + "runtime" "strconv" "strings" "sync" @@ -13,8 +18,6 @@ import ( "github.com/containerd/log" "github.com/distribution/reference" "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/internal/lazyregexp" - "github.com/docker/docker/pkg/homedir" ) // ServiceOptions holds command line options. @@ -58,52 +61,36 @@ var ( Host: DefaultRegistryHost, } - validHostPortRegex = lazyregexp.New(`^` + reference.DomainRegexp.String() + `$`) - - // certsDir is used to override defaultCertsDir when running with rootlessKit. - // - // TODO(thaJeztah): change to a sync.OnceValue once we remove [SetCertsDir] - // TODO(thaJeztah): certsDir should not be a package variable, but stored in our config, and passed when needed. - setCertsDirOnce sync.Once - certsDir string -) - -func setCertsDir(dir string) string { - setCertsDirOnce.Do(func() { - if dir != "" { - certsDir = dir - return - } - if os.Getenv("ROOTLESSKIT_STATE_DIR") != "" { - // Configure registry.CertsDir() when running in rootless-mode - // This is the equivalent of [rootless.RunningWithRootlessKit], - // but inlining it to prevent adding that as a dependency - // for docker/cli. - // - // [rootless.RunningWithRootlessKit]: https://github.com/moby/moby/blob/b4bdf12daec84caaf809a639f923f7370d4926ad/pkg/rootless/rootless.go#L5-L8 - if configHome, _ := homedir.GetConfigHome(); configHome != "" { - certsDir = filepath.Join(configHome, "docker/certs.d") - return - } - } - certsDir = defaultCertsDir + validHostPortRegex = sync.OnceValue(func() *regexp.Regexp { + return regexp.MustCompile(`^` + reference.DomainRegexp.String() + `$`) }) - return certsDir -} +) -// SetCertsDir allows the default certs directory to be changed. This function -// is used at daemon startup to set the correct location when running in -// rootless mode. +// runningWithRootlessKit is a fork of [rootless.RunningWithRootlessKit], +// but inlining it to prevent adding that as a dependency for docker/cli. // -// Deprecated: the cert-directory is now automatically selected when running with rootlessKit, and should no longer be set manually. -func SetCertsDir(path string) { - setCertsDir(path) +// [rootless.RunningWithRootlessKit]: https://github.com/moby/moby/blob/b4bdf12daec84caaf809a639f923f7370d4926ad/pkg/rootless/rootless.go#L5-L8 +func runningWithRootlessKit() bool { + return runtime.GOOS == "linux" && os.Getenv("ROOTLESSKIT_STATE_DIR") != "" } // CertsDir is the directory where certificates are stored. +// +// - Linux: "/etc/docker/certs.d/" +// - Linux (with rootlessKit): $XDG_CONFIG_HOME/docker/certs.d/" or "$HOME/.config/docker/certs.d/" +// - Windows: "%PROGRAMDATA%/docker/certs.d/" +// +// TODO(thaJeztah): certsDir but stored in our config, and passed when needed. For the CLI, we should also default to same path as rootless. func CertsDir() string { - // call setCertsDir with an empty path to synchronise with [SetCertsDir] - return setCertsDir("") + certsDir := "/etc/docker/certs.d" + if runningWithRootlessKit() { + if configHome, _ := os.UserConfigDir(); configHome != "" { + certsDir = filepath.Join(configHome, "docker", "certs.d") + } + } else if runtime.GOOS == "windows" { + certsDir = filepath.Join(os.Getenv("programdata"), "docker", "certs.d") + } + return certsDir } // newServiceConfig returns a new instance of ServiceConfig @@ -181,14 +168,15 @@ skip: if _, err := ValidateIndexName(r); err != nil { return err } - if strings.HasPrefix(strings.ToLower(r), "http://") { - log.G(context.TODO()).Warnf("insecure registry %s should not contain 'http://' and 'http://' has been removed from the insecure registry config", r) - r = r[7:] - } else if strings.HasPrefix(strings.ToLower(r), "https://") { - log.G(context.TODO()).Warnf("insecure registry %s should not contain 'https://' and 'https://' has been removed from the insecure registry config", r) - r = r[8:] - } else if hasScheme(r) { - return invalidParamf("insecure registry %s should not contain '://'", r) + if scheme, host, ok := strings.Cut(r, "://"); ok { + switch strings.ToLower(scheme) { + case "http", "https": + log.G(context.TODO()).Warnf("insecure registry %[1]s should not contain '%[2]s' and '%[2]ss' has been removed from the insecure registry config", r, scheme) + r = host + default: + // unsupported scheme + return invalidParamf("insecure registry %s should not contain '://'", r) + } } // Check if CIDR was passed to --insecure-registry _, ipnet, err := net.ParseCIDR(r) @@ -253,18 +241,18 @@ func (config *serviceConfig) isSecureIndex(indexName string) bool { // for mocking in unit tests. var lookupIP = net.LookupIP -// isCIDRMatch returns true if URLHost matches an element of cidrs. URLHost is a URL.Host (`host:port` or `host`) +// isCIDRMatch returns true if urlHost matches an element of cidrs. urlHost is a URL.Host ("host:port" or "host") // where the `host` part can be either a domain name or an IP address. If it is a domain name, then it will be // resolved to IP addresses for matching. If resolution fails, false is returned. -func isCIDRMatch(cidrs []*registry.NetIPNet, URLHost string) bool { +func isCIDRMatch(cidrs []*registry.NetIPNet, urlHost string) bool { if len(cidrs) == 0 { return false } - host, _, err := net.SplitHostPort(URLHost) + host, _, err := net.SplitHostPort(urlHost) if err != nil { - // Assume URLHost is a host without port and go on. - host = URLHost + // Assume urlHost is a host without port and go on. + host = urlHost } var addresses []net.IP @@ -353,7 +341,7 @@ func validateHostPort(s string) error { } // If match against the `host:port` pattern fails, // it might be `IPv6:port`, which will be captured by net.ParseIP(host) - if !validHostPortRegex.MatchString(s) && net.ParseIP(host) == nil { + if !validHostPortRegex().MatchString(s) && net.ParseIP(host) == nil { return invalidParamf("invalid host %q", host) } if port != "" { @@ -394,25 +382,6 @@ func GetAuthConfigKey(index *registry.IndexInfo) string { return index.Name } -// newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo -func newRepositoryInfo(config *serviceConfig, name reference.Named) *RepositoryInfo { - index := newIndexInfo(config, reference.Domain(name)) - var officialRepo bool - if index.Official { - // RepositoryInfo.Official indicates whether the image repository - // is an official (docker library official images) repository. - // - // We only need to check this if the image-repository is on Docker Hub. - officialRepo = !strings.ContainsRune(reference.FamiliarName(name), '/') - } - - return &RepositoryInfo{ - Name: reference.TrimNamed(name), - Index: index, - Official: officialRepo, - } -} - // ParseRepositoryInfo performs the breakdown of a repository name into a // [RepositoryInfo], but lacks registry configuration. // @@ -428,7 +397,6 @@ func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) { Secure: true, Official: true, }, - Official: !strings.ContainsRune(reference.FamiliarName(reposName), '/'), }, nil } diff --git a/internal/registry/config_test.go b/internal/registry/config_test.go new file mode 100644 index 000000000000..699e763be6cf --- /dev/null +++ b/internal/registry/config_test.go @@ -0,0 +1,340 @@ +package registry + +import ( + "testing" + + cerrdefs "github.com/containerd/errdefs" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestValidateMirror(t *testing.T) { + tests := []struct { + input string + output string + expectedErr string + }{ + // Valid cases + { + input: "http://mirror-1.example.com", + output: "http://mirror-1.example.com/", + }, + { + input: "http://mirror-1.example.com/", + output: "http://mirror-1.example.com/", + }, + { + input: "https://mirror-1.example.com", + output: "https://mirror-1.example.com/", + }, + { + input: "https://mirror-1.example.com/", + output: "https://mirror-1.example.com/", + }, + { + input: "http://localhost", + output: "http://localhost/", + }, + { + input: "https://localhost", + output: "https://localhost/", + }, + { + input: "http://localhost:5000", + output: "http://localhost:5000/", + }, + { + input: "https://localhost:5000", + output: "https://localhost:5000/", + }, + { + input: "http://127.0.0.1", + output: "http://127.0.0.1/", + }, + { + input: "https://127.0.0.1", + output: "https://127.0.0.1/", + }, + { + input: "http://127.0.0.1:5000", + output: "http://127.0.0.1:5000/", + }, + { + input: "https://127.0.0.1:5000", + output: "https://127.0.0.1:5000/", + }, + { + input: "http://mirror-1.example.com/v1/", + output: "http://mirror-1.example.com/v1/", + }, + { + input: "https://mirror-1.example.com/v1/", + output: "https://mirror-1.example.com/v1/", + }, + + // Invalid cases + { + input: "!invalid!://%as%", + expectedErr: `invalid mirror: "!invalid!://%as%" is not a valid URI: parse "!invalid!://%as%": first path segment in URL cannot contain colon`, + }, + { + input: "mirror-1.example.com", + expectedErr: `invalid mirror: no scheme specified for "mirror-1.example.com": must use either 'https://' or 'http://'`, + }, + { + input: "mirror-1.example.com:5000", + expectedErr: `invalid mirror: no scheme specified for "mirror-1.example.com:5000": must use either 'https://' or 'http://'`, + }, + { + input: "ftp://mirror-1.example.com", + expectedErr: `invalid mirror: unsupported scheme "ftp" in "ftp://mirror-1.example.com": must use either 'https://' or 'http://'`, + }, + { + input: "http://mirror-1.example.com/?q=foo", + expectedErr: `invalid mirror: query or fragment at end of the URI "http://mirror-1.example.com/?q=foo"`, + }, + { + input: "http://mirror-1.example.com/v1/?q=foo", + expectedErr: `invalid mirror: query or fragment at end of the URI "http://mirror-1.example.com/v1/?q=foo"`, + }, + { + input: "http://mirror-1.example.com/v1/?q=foo#frag", + expectedErr: `invalid mirror: query or fragment at end of the URI "http://mirror-1.example.com/v1/?q=foo#frag"`, + }, + { + input: "http://mirror-1.example.com?q=foo", + expectedErr: `invalid mirror: query or fragment at end of the URI "http://mirror-1.example.com?q=foo"`, + }, + { + input: "https://mirror-1.example.com#frag", + expectedErr: `invalid mirror: query or fragment at end of the URI "https://mirror-1.example.com#frag"`, + }, + { + input: "https://mirror-1.example.com/#frag", + expectedErr: `invalid mirror: query or fragment at end of the URI "https://mirror-1.example.com/#frag"`, + }, + { + input: "http://foo:bar@mirror-1.example.com/", + expectedErr: `invalid mirror: username/password not allowed in URI "http://foo:xxxxx@mirror-1.example.com/"`, + }, + { + input: "https://mirror-1.example.com/v1/#frag", + expectedErr: `invalid mirror: query or fragment at end of the URI "https://mirror-1.example.com/v1/#frag"`, + }, + { + input: "https://mirror-1.example.com?q", + expectedErr: `invalid mirror: query or fragment at end of the URI "https://mirror-1.example.com?q"`, + }, + } + + for _, tc := range tests { + t.Run(tc.input, func(t *testing.T) { + out, err := ValidateMirror(tc.input) + if tc.expectedErr != "" { + assert.Error(t, err, tc.expectedErr) + } else { + assert.NilError(t, err) + } + assert.Check(t, is.Equal(out, tc.output)) + }) + } +} + +func TestLoadInsecureRegistries(t *testing.T) { + testCases := []struct { + registries []string + index string + err string + }{ + { + registries: []string{"127.0.0.1"}, + index: "127.0.0.1", + }, + { + registries: []string{"127.0.0.1:8080"}, + index: "127.0.0.1:8080", + }, + { + registries: []string{"2001:db8::1"}, + index: "2001:db8::1", + }, + { + registries: []string{"[2001:db8::1]:80"}, + index: "[2001:db8::1]:80", + }, + { + registries: []string{"http://myregistry.example.com"}, + index: "myregistry.example.com", + }, + { + registries: []string{"https://myregistry.example.com"}, + index: "myregistry.example.com", + }, + { + registries: []string{"HTTP://myregistry.example.com"}, + index: "myregistry.example.com", + }, + { + registries: []string{"svn://myregistry.example.com"}, + err: "insecure registry svn://myregistry.example.com should not contain '://'", + }, + { + registries: []string{"-invalid-registry"}, + err: "Cannot begin or end with a hyphen", + }, + { + registries: []string{`mytest-.com`}, + err: `insecure registry mytest-.com is not valid: invalid host "mytest-.com"`, + }, + { + registries: []string{`1200:0000:AB00:1234:0000:2552:7777:1313:8080`}, + err: `insecure registry 1200:0000:AB00:1234:0000:2552:7777:1313:8080 is not valid: invalid host "1200:0000:AB00:1234:0000:2552:7777:1313:8080"`, + }, + { + registries: []string{`myregistry.example.com:500000`}, + err: `insecure registry myregistry.example.com:500000 is not valid: invalid port "500000"`, + }, + { + registries: []string{`"myregistry.example.com"`}, + err: `insecure registry "myregistry.example.com" is not valid: invalid host "\"myregistry.example.com\""`, + }, + { + registries: []string{`"myregistry.example.com:5000"`}, + err: `insecure registry "myregistry.example.com:5000" is not valid: invalid host "\"myregistry.example.com"`, + }, + } + for _, testCase := range testCases { + config := &serviceConfig{} + err := config.loadInsecureRegistries(testCase.registries) + if testCase.err == "" { + if err != nil { + t.Fatalf("expect no error, got '%s'", err) + } + match := false + for index := range config.IndexConfigs { + if index == testCase.index { + match = true + } + } + if !match { + t.Fatalf("expect index configs to contain '%s', got %+v", testCase.index, config.IndexConfigs) + } + } else { + if err == nil { + t.Fatalf("expect error '%s', got no error", testCase.err) + } + assert.ErrorContains(t, err, testCase.err) + assert.Check(t, cerrdefs.IsInvalidArgument(err)) + } + } +} + +func TestNewServiceConfig(t *testing.T) { + tests := []struct { + doc string + opts ServiceOptions + errStr string + }{ + { + doc: "empty config", + }, + { + doc: "invalid mirror", + opts: ServiceOptions{ + Mirrors: []string{"example.com:5000"}, + }, + errStr: `invalid mirror: no scheme specified for "example.com:5000": must use either 'https://' or 'http://'`, + }, + { + doc: "valid mirror", + opts: ServiceOptions{ + Mirrors: []string{"https://example.com:5000"}, + }, + }, + { + doc: "invalid insecure registry", + opts: ServiceOptions{ + InsecureRegistries: []string{"[fe80::]/64"}, + }, + errStr: `insecure registry [fe80::]/64 is not valid: invalid host "[fe80::]/64"`, + }, + { + doc: "valid insecure registry", + opts: ServiceOptions{ + InsecureRegistries: []string{"102.10.8.1/24"}, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.doc, func(t *testing.T) { + _, err := newServiceConfig(tc.opts) + if tc.errStr != "" { + assert.Check(t, is.Error(err, tc.errStr)) + assert.Check(t, cerrdefs.IsInvalidArgument(err)) + } else { + assert.Check(t, err) + } + }) + } +} + +func TestValidateIndexName(t *testing.T) { + valid := []struct { + index string + expect string + }{ + { + index: "index.docker.io", + expect: "docker.io", + }, + { + index: "example.com", + expect: "example.com", + }, + { + index: "127.0.0.1:8080", + expect: "127.0.0.1:8080", + }, + { + index: "mytest-1.com", + expect: "mytest-1.com", + }, + { + index: "mirror-1.example.com/v1/?q=foo", + expect: "mirror-1.example.com/v1/?q=foo", + }, + } + + for _, testCase := range valid { + result, err := ValidateIndexName(testCase.index) + if assert.Check(t, err) { + assert.Check(t, is.Equal(testCase.expect, result)) + } + } +} + +func TestValidateIndexNameWithError(t *testing.T) { + invalid := []struct { + index string + err string + }{ + { + index: "docker.io-", + err: "invalid index name (docker.io-). Cannot begin or end with a hyphen", + }, + { + index: "-example.com", + err: "invalid index name (-example.com). Cannot begin or end with a hyphen", + }, + { + index: "mirror-1.example.com/v1/?q=foo-", + err: "invalid index name (mirror-1.example.com/v1/?q=foo-). Cannot begin or end with a hyphen", + }, + } + for _, testCase := range invalid { + _, err := ValidateIndexName(testCase.index) + assert.Check(t, is.Error(err, testCase.err)) + assert.Check(t, cerrdefs.IsInvalidArgument(err)) + } +} diff --git a/internal/registry/doc.go b/internal/registry/doc.go new file mode 100644 index 000000000000..0b6a24767c84 --- /dev/null +++ b/internal/registry/doc.go @@ -0,0 +1,12 @@ +// Package registry is a fork of [github.com/docker/docker/registry], taken +// at commit [moby@49306c6]. Git history was not preserved in this fork, +// but can be found using the URLs provided. +// +// This fork was created to remove the dependency on the "Moby" codebase, +// and because the CLI only needs a subset of its features. The original +// package was written specifically for use in the daemon code, and includes +// functionality that cannot be used in the CLI. +// +// [github.com/docker/docker/registry]: https://pkg.go.dev/github.com/docker/docker@v28.3.2+incompatible/registry +// [moby@49306c6]: https://github.com/moby/moby/tree/49306c607b72c5bf0a8e426f5a9760fa5ef96ea0/registry +package registry diff --git a/vendor/github.com/docker/docker/registry/errors.go b/internal/registry/errors.go similarity index 85% rename from vendor/github.com/docker/docker/registry/errors.go rename to internal/registry/errors.go index cc3a37da6e92..d37155a789de 100644 --- a/vendor/github.com/docker/docker/registry/errors.go +++ b/internal/registry/errors.go @@ -8,17 +8,13 @@ import ( ) func translateV2AuthError(err error) error { - switch e := err.(type) { - case *url.Error: - switch e2 := e.Err.(type) { - case errcode.Error: - switch e2.Code { - case errcode.ErrorCodeUnauthorized: - return unauthorizedErr{err} - } + var e *url.Error + if errors.As(err, &e) { + var e2 errcode.Error + if errors.As(e, &e2) && errors.Is(e2.Code, errcode.ErrorCodeUnauthorized) { + return unauthorizedErr{err} } } - return err } diff --git a/vendor/github.com/docker/docker/registry/registry.go b/internal/registry/registry.go similarity index 91% rename from vendor/github.com/docker/docker/registry/registry.go rename to internal/registry/registry.go index d3b3fbc9baef..d69c1d9ec7b3 100644 --- a/vendor/github.com/docker/docker/registry/registry.go +++ b/internal/registry/registry.go @@ -8,6 +8,8 @@ import ( "net/http" "os" "path/filepath" + "runtime" + "strings" "time" "github.com/containerd/log" @@ -16,16 +18,15 @@ import ( "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) -// HostCertsDir returns the config directory for a specific host. -// -// Deprecated: this function was only used internally, and will be removed in a future release. -func HostCertsDir(hostname string) string { - return hostCertsDir(hostname) -} - // hostCertsDir returns the config directory for a specific host. -func hostCertsDir(hostname string) string { - return filepath.Join(CertsDir(), cleanPath(hostname)) +func hostCertsDir(hostnameAndPort string) string { + if runtime.GOOS == "windows" { + // Ensure that a directory name is valid; hostnameAndPort may contain + // a colon (:) if a port is included, and Windows does not allow colons + // in directory names. + hostnameAndPort = filepath.FromSlash(strings.ReplaceAll(hostnameAndPort, ":", "")) + } + return filepath.Join(CertsDir(), hostnameAndPort) } // newTLSConfig constructs a client TLS configuration based on server defaults diff --git a/internal/registry/registry_mock_test.go b/internal/registry/registry_mock_test.go new file mode 100644 index 000000000000..eb6b16de790d --- /dev/null +++ b/internal/registry/registry_mock_test.go @@ -0,0 +1,120 @@ +package registry + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/containerd/log" + "github.com/docker/docker/api/types/registry" + "gotest.tools/v3/assert" +) + +var ( + testHTTPServer *httptest.Server + testHTTPSServer *httptest.Server +) + +func init() { + r := http.NewServeMux() + + // /v1/ + r.HandleFunc("/v1/_ping", handlerGetPing) + r.HandleFunc("/v1/search", handlerSearch) + + // /v2/ + r.HandleFunc("/v2/version", handlerGetPing) + + testHTTPServer = httptest.NewServer(handlerAccessLog(r)) + testHTTPSServer = httptest.NewTLSServer(handlerAccessLog(r)) +} + +func handlerAccessLog(handler http.Handler) http.Handler { + logHandler := func(w http.ResponseWriter, r *http.Request) { + log.G(context.TODO()).Debugf(`%s "%s %s"`, r.RemoteAddr, r.Method, r.URL) + handler.ServeHTTP(w, r) + } + return http.HandlerFunc(logHandler) +} + +func makeURL(req string) string { + return testHTTPServer.URL + req +} + +func makeHTTPSURL(req string) string { + return testHTTPSServer.URL + req +} + +func makeIndex(req string) *registry.IndexInfo { + return ®istry.IndexInfo{ + Name: makeURL(req), + } +} + +func makeHTTPSIndex(req string) *registry.IndexInfo { + return ®istry.IndexInfo{ + Name: makeHTTPSURL(req), + } +} + +func makePublicIndex() *registry.IndexInfo { + return ®istry.IndexInfo{ + Name: IndexServer, + Secure: true, + Official: true, + } +} + +func writeHeaders(w http.ResponseWriter) { + h := w.Header() + h.Add("Server", "docker-tests/mock") + h.Add("Expires", "-1") + h.Add("Content-Type", "application/json") + h.Add("Pragma", "no-cache") + h.Add("Cache-Control", "no-cache") +} + +func writeResponse(w http.ResponseWriter, message interface{}, code int) { + writeHeaders(w) + w.WriteHeader(code) + body, err := json.Marshal(message) + if err != nil { + _, _ = io.WriteString(w, err.Error()) + return + } + _, _ = w.Write(body) +} + +func handlerGetPing(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + writeResponse(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + writeResponse(w, true, http.StatusOK) +} + +func handlerSearch(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + writeResponse(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + result := ®istry.SearchResults{ + Query: "fakequery", + NumResults: 1, + Results: []registry.SearchResult{{Name: "fakeimage", StarCount: 42}}, + } + writeResponse(w, result, http.StatusOK) +} + +func TestPing(t *testing.T) { + res, err := http.Get(makeURL("/v1/_ping")) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, res.StatusCode, http.StatusOK, "") + assert.Equal(t, res.Header.Get("Server"), "docker-tests/mock") + _ = res.Body.Close() +} diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go new file mode 100644 index 000000000000..8495174c36b2 --- /dev/null +++ b/internal/registry/registry_test.go @@ -0,0 +1,637 @@ +package registry + +import ( + "errors" + "net" + "testing" + + "github.com/distribution/reference" + "github.com/docker/docker/api/types/registry" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +// overrideLookupIP overrides net.LookupIP for testing. +func overrideLookupIP(t *testing.T) { + t.Helper() + restoreLookup := lookupIP + + // override net.LookupIP + lookupIP = func(host string) ([]net.IP, error) { + mockHosts := map[string][]net.IP{ + "": {net.ParseIP("0.0.0.0")}, + "localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, + "example.com": {net.ParseIP("42.42.42.42")}, + "other.com": {net.ParseIP("43.43.43.43")}, + } + if addrs, ok := mockHosts[host]; ok { + return addrs, nil + } + return nil, errors.New("lookup: no such host") + } + t.Cleanup(func() { + lookupIP = restoreLookup + }) +} + +func TestParseRepositoryInfo(t *testing.T) { + type staticRepositoryInfo struct { + Index *registry.IndexInfo + RemoteName string + CanonicalName string + LocalName string + } + + tests := map[string]staticRepositoryInfo{ + "fooo/bar": { + Index: ®istry.IndexInfo{ + Name: IndexName, + Mirrors: []string{}, + Official: true, + Secure: true, + }, + RemoteName: "fooo/bar", + LocalName: "fooo/bar", + CanonicalName: "docker.io/fooo/bar", + }, + "library/ubuntu": { + Index: ®istry.IndexInfo{ + Name: IndexName, + Mirrors: []string{}, + Official: true, + Secure: true, + }, + RemoteName: "library/ubuntu", + LocalName: "ubuntu", + CanonicalName: "docker.io/library/ubuntu", + }, + "nonlibrary/ubuntu": { + Index: ®istry.IndexInfo{ + Name: IndexName, + Mirrors: []string{}, + Official: true, + Secure: true, + }, + RemoteName: "nonlibrary/ubuntu", + LocalName: "nonlibrary/ubuntu", + CanonicalName: "docker.io/nonlibrary/ubuntu", + }, + "ubuntu": { + Index: ®istry.IndexInfo{ + Name: IndexName, + Mirrors: []string{}, + Official: true, + Secure: true, + }, + RemoteName: "library/ubuntu", + LocalName: "ubuntu", + CanonicalName: "docker.io/library/ubuntu", + }, + "other/library": { + Index: ®istry.IndexInfo{ + Name: IndexName, + Mirrors: []string{}, + Official: true, + Secure: true, + }, + RemoteName: "other/library", + LocalName: "other/library", + CanonicalName: "docker.io/other/library", + }, + "127.0.0.1:8000/private/moonbase": { + Index: ®istry.IndexInfo{ + Name: "127.0.0.1:8000", + Mirrors: []string{}, + Official: false, + Secure: false, + }, + RemoteName: "private/moonbase", + LocalName: "127.0.0.1:8000/private/moonbase", + CanonicalName: "127.0.0.1:8000/private/moonbase", + }, + "127.0.0.1:8000/privatebase": { + Index: ®istry.IndexInfo{ + Name: "127.0.0.1:8000", + Mirrors: []string{}, + Official: false, + Secure: false, + }, + RemoteName: "privatebase", + LocalName: "127.0.0.1:8000/privatebase", + CanonicalName: "127.0.0.1:8000/privatebase", + }, + "[::1]:8000/private/moonbase": { + Index: ®istry.IndexInfo{ + Name: "[::1]:8000", + Mirrors: []string{}, + Official: false, + Secure: false, + }, + RemoteName: "private/moonbase", + LocalName: "[::1]:8000/private/moonbase", + CanonicalName: "[::1]:8000/private/moonbase", + }, + "[::1]:8000/privatebase": { + Index: ®istry.IndexInfo{ + Name: "[::1]:8000", + Mirrors: []string{}, + Official: false, + Secure: false, + }, + RemoteName: "privatebase", + LocalName: "[::1]:8000/privatebase", + CanonicalName: "[::1]:8000/privatebase", + }, + // IPv6 only has a single loopback address, so ::2 is not a loopback, + // hence not marked "insecure". + "[::2]:8000/private/moonbase": { + Index: ®istry.IndexInfo{ + Name: "[::2]:8000", + Mirrors: []string{}, + Official: false, + Secure: true, + }, + RemoteName: "private/moonbase", + LocalName: "[::2]:8000/private/moonbase", + CanonicalName: "[::2]:8000/private/moonbase", + }, + // IPv6 only has a single loopback address, so ::2 is not a loopback, + // hence not marked "insecure". + "[::2]:8000/privatebase": { + Index: ®istry.IndexInfo{ + Name: "[::2]:8000", + Mirrors: []string{}, + Official: false, + Secure: true, + }, + RemoteName: "privatebase", + LocalName: "[::2]:8000/privatebase", + CanonicalName: "[::2]:8000/privatebase", + }, + "localhost:8000/private/moonbase": { + Index: ®istry.IndexInfo{ + Name: "localhost:8000", + Mirrors: []string{}, + Official: false, + Secure: false, + }, + RemoteName: "private/moonbase", + LocalName: "localhost:8000/private/moonbase", + CanonicalName: "localhost:8000/private/moonbase", + }, + "localhost:8000/privatebase": { + Index: ®istry.IndexInfo{ + Name: "localhost:8000", + Mirrors: []string{}, + Official: false, + Secure: false, + }, + RemoteName: "privatebase", + LocalName: "localhost:8000/privatebase", + CanonicalName: "localhost:8000/privatebase", + }, + "example.com/private/moonbase": { + Index: ®istry.IndexInfo{ + Name: "example.com", + Mirrors: []string{}, + Official: false, + Secure: true, + }, + RemoteName: "private/moonbase", + LocalName: "example.com/private/moonbase", + CanonicalName: "example.com/private/moonbase", + }, + "example.com/privatebase": { + Index: ®istry.IndexInfo{ + Name: "example.com", + Mirrors: []string{}, + Official: false, + Secure: true, + }, + RemoteName: "privatebase", + LocalName: "example.com/privatebase", + CanonicalName: "example.com/privatebase", + }, + "example.com:8000/private/moonbase": { + Index: ®istry.IndexInfo{ + Name: "example.com:8000", + Mirrors: []string{}, + Official: false, + Secure: true, + }, + RemoteName: "private/moonbase", + LocalName: "example.com:8000/private/moonbase", + CanonicalName: "example.com:8000/private/moonbase", + }, + "example.com:8000/privatebase": { + Index: ®istry.IndexInfo{ + Name: "example.com:8000", + Mirrors: []string{}, + Official: false, + Secure: true, + }, + RemoteName: "privatebase", + LocalName: "example.com:8000/privatebase", + CanonicalName: "example.com:8000/privatebase", + }, + "localhost/private/moonbase": { + Index: ®istry.IndexInfo{ + Name: "localhost", + Mirrors: []string{}, + Official: false, + Secure: false, + }, + RemoteName: "private/moonbase", + LocalName: "localhost/private/moonbase", + CanonicalName: "localhost/private/moonbase", + }, + "localhost/privatebase": { + Index: ®istry.IndexInfo{ + Name: "localhost", + Mirrors: []string{}, + Official: false, + Secure: false, + }, + RemoteName: "privatebase", + LocalName: "localhost/privatebase", + CanonicalName: "localhost/privatebase", + }, + IndexName + "/public/moonbase": { + Index: ®istry.IndexInfo{ + Name: IndexName, + Mirrors: []string{}, + Official: true, + Secure: true, + }, + RemoteName: "public/moonbase", + LocalName: "public/moonbase", + CanonicalName: "docker.io/public/moonbase", + }, + "index." + IndexName + "/public/moonbase": { + Index: ®istry.IndexInfo{ + Name: IndexName, + Mirrors: []string{}, + Official: true, + Secure: true, + }, + RemoteName: "public/moonbase", + LocalName: "public/moonbase", + CanonicalName: "docker.io/public/moonbase", + }, + "ubuntu-12.04-base": { + Index: ®istry.IndexInfo{ + Name: IndexName, + Mirrors: []string{}, + Official: true, + Secure: true, + }, + RemoteName: "library/ubuntu-12.04-base", + LocalName: "ubuntu-12.04-base", + CanonicalName: "docker.io/library/ubuntu-12.04-base", + }, + IndexName + "/ubuntu-12.04-base": { + Index: ®istry.IndexInfo{ + Name: IndexName, + Mirrors: []string{}, + Official: true, + Secure: true, + }, + RemoteName: "library/ubuntu-12.04-base", + LocalName: "ubuntu-12.04-base", + CanonicalName: "docker.io/library/ubuntu-12.04-base", + }, + "index." + IndexName + "/ubuntu-12.04-base": { + Index: ®istry.IndexInfo{ + Name: IndexName, + Mirrors: []string{}, + Official: true, + Secure: true, + }, + RemoteName: "library/ubuntu-12.04-base", + LocalName: "ubuntu-12.04-base", + CanonicalName: "docker.io/library/ubuntu-12.04-base", + }, + } + + for reposName, expected := range tests { + t.Run(reposName, func(t *testing.T) { + named, err := reference.ParseNormalizedNamed(reposName) + assert.NilError(t, err) + + repoInfo, err := ParseRepositoryInfo(named) + assert.NilError(t, err) + + assert.Check(t, is.DeepEqual(repoInfo.Index, expected.Index)) + assert.Check(t, is.Equal(reference.Path(repoInfo.Name), expected.RemoteName)) + assert.Check(t, is.Equal(reference.FamiliarName(repoInfo.Name), expected.LocalName)) + assert.Check(t, is.Equal(repoInfo.Name.Name(), expected.CanonicalName)) + }) + } +} + +func TestNewIndexInfo(t *testing.T) { + overrideLookupIP(t) + + // ipv6Loopback is the CIDR for the IPv6 loopback address ("::1"); "::1/128" + ipv6Loopback := &net.IPNet{ + IP: net.IPv6loopback, + Mask: net.CIDRMask(128, 128), + } + + // ipv4Loopback is the CIDR for IPv4 loopback addresses ("127.0.0.0/8") + ipv4Loopback := &net.IPNet{ + IP: net.IPv4(127, 0, 0, 0), + Mask: net.CIDRMask(8, 32), + } + + // emptyServiceConfig is a default service-config for situations where + // no config-file is available (e.g. when used in the CLI). It won't + // have mirrors configured, but does have the default insecure registry + // CIDRs for loopback interfaces configured. + emptyServiceConfig := &serviceConfig{ + IndexConfigs: map[string]*registry.IndexInfo{ + IndexName: { + Name: IndexName, + Mirrors: []string{}, + Secure: true, + Official: true, + }, + }, + InsecureRegistryCIDRs: []*registry.NetIPNet{ + (*registry.NetIPNet)(ipv6Loopback), + (*registry.NetIPNet)(ipv4Loopback), + }, + } + + expectedIndexInfos := map[string]*registry.IndexInfo{ + IndexName: { + Name: IndexName, + Official: true, + Secure: true, + Mirrors: []string{}, + }, + "index." + IndexName: { + Name: IndexName, + Official: true, + Secure: true, + Mirrors: []string{}, + }, + "example.com": { + Name: "example.com", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + "127.0.0.1:5000": { + Name: "127.0.0.1:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + } + t.Run("no mirrors", func(t *testing.T) { + for indexName, expected := range expectedIndexInfos { + t.Run(indexName, func(t *testing.T) { + actual := newIndexInfo(emptyServiceConfig, indexName) + assert.Check(t, is.DeepEqual(actual, expected)) + }) + } + }) + + expectedIndexInfos = map[string]*registry.IndexInfo{ + IndexName: { + Name: IndexName, + Official: true, + Secure: true, + Mirrors: []string{"http://mirror1.local/", "http://mirror2.local/"}, + }, + "index." + IndexName: { + Name: IndexName, + Official: true, + Secure: true, + Mirrors: []string{"http://mirror1.local/", "http://mirror2.local/"}, + }, + "example.com": { + Name: "example.com", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "example.com:5000": { + Name: "example.com:5000", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + "127.0.0.1": { + Name: "127.0.0.1", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "127.0.0.1:5000": { + Name: "127.0.0.1:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "127.255.255.255": { + Name: "127.255.255.255", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "127.255.255.255:5000": { + Name: "127.255.255.255:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "::1": { + Name: "::1", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "[::1]:5000": { + Name: "[::1]:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + // IPv6 only has a single loopback address, so ::2 is not a loopback, + // hence not marked "insecure". + "::2": { + Name: "::2", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + // IPv6 only has a single loopback address, so ::2 is not a loopback, + // hence not marked "insecure". + "[::2]:5000": { + Name: "[::2]:5000", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + "other.com": { + Name: "other.com", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + } + t.Run("mirrors", func(t *testing.T) { + // Note that newServiceConfig calls ValidateMirror internally, which normalizes + // mirror-URLs to have a trailing slash. + config, err := newServiceConfig(ServiceOptions{ + Mirrors: []string{"http://mirror1.local", "http://mirror2.local"}, + InsecureRegistries: []string{"example.com"}, + }) + assert.NilError(t, err) + for indexName, expected := range expectedIndexInfos { + t.Run(indexName, func(t *testing.T) { + actual := newIndexInfo(config, indexName) + assert.Check(t, is.DeepEqual(actual, expected)) + }) + } + }) + + expectedIndexInfos = map[string]*registry.IndexInfo{ + "example.com": { + Name: "example.com", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "example.com:5000": { + Name: "example.com:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "127.0.0.1": { + Name: "127.0.0.1", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "127.0.0.1:5000": { + Name: "127.0.0.1:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "42.42.0.1:5000": { + Name: "42.42.0.1:5000", + Official: false, + Secure: false, + Mirrors: []string{}, + }, + "42.43.0.1:5000": { + Name: "42.43.0.1:5000", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + "other.com": { + Name: "other.com", + Official: false, + Secure: true, + Mirrors: []string{}, + }, + } + t.Run("custom insecure", func(t *testing.T) { + config, err := newServiceConfig(ServiceOptions{ + InsecureRegistries: []string{"42.42.0.0/16"}, + }) + assert.NilError(t, err) + for indexName, expected := range expectedIndexInfos { + t.Run(indexName, func(t *testing.T) { + actual := newIndexInfo(config, indexName) + assert.Check(t, is.DeepEqual(actual, expected)) + }) + } + }) +} + +func TestMirrorEndpointLookup(t *testing.T) { + containsMirror := func(endpoints []APIEndpoint) bool { + for _, pe := range endpoints { + if pe.URL.Host == "my.mirror" { + return true + } + } + return false + } + cfg, err := newServiceConfig(ServiceOptions{ + Mirrors: []string{"https://my.mirror"}, + }) + assert.NilError(t, err) + s := Service{config: cfg} + + imageName, err := reference.WithName(IndexName + "/test/image") + if err != nil { + t.Error(err) + } + pushAPIEndpoints, err := s.LookupPushEndpoints(reference.Domain(imageName)) + if err != nil { + t.Fatal(err) + } + if containsMirror(pushAPIEndpoints) { + t.Fatal("Push endpoint should not contain mirror") + } + + pullAPIEndpoints, err := s.LookupPullEndpoints(reference.Domain(imageName)) + if err != nil { + t.Fatal(err) + } + if !containsMirror(pullAPIEndpoints) { + t.Fatal("Pull endpoint should contain mirror") + } +} + +func TestIsSecureIndex(t *testing.T) { + overrideLookupIP(t) + tests := []struct { + addr string + insecureRegistries []string + expected bool + }{ + {IndexName, nil, true}, + {"example.com", []string{}, true}, + {"example.com", []string{"example.com"}, false}, + {"localhost", []string{"localhost:5000"}, false}, + {"localhost:5000", []string{"localhost:5000"}, false}, + {"localhost", []string{"example.com"}, false}, + {"127.0.0.1:5000", []string{"127.0.0.1:5000"}, false}, + {"localhost", nil, false}, + {"localhost:5000", nil, false}, + {"127.0.0.1", nil, false}, + {"localhost", []string{"example.com"}, false}, + {"127.0.0.1", []string{"example.com"}, false}, + {"example.com", nil, true}, + {"example.com", []string{"example.com"}, false}, + {"127.0.0.1", []string{"example.com"}, false}, + {"127.0.0.1:5000", []string{"example.com"}, false}, + {"example.com:5000", []string{"42.42.0.0/16"}, false}, + {"example.com", []string{"42.42.0.0/16"}, false}, + {"example.com:5000", []string{"42.42.42.42/8"}, false}, + {"127.0.0.1:5000", []string{"127.0.0.0/8"}, false}, + {"42.42.42.42:5000", []string{"42.1.1.1/8"}, false}, + {"invalid.example.com", []string{"42.42.0.0/16"}, true}, + {"invalid.example.com", []string{"invalid.example.com"}, false}, + {"invalid.example.com:5000", []string{"invalid.example.com"}, true}, + {"invalid.example.com:5000", []string{"invalid.example.com:5000"}, false}, + } + for _, tc := range tests { + config, err := newServiceConfig(ServiceOptions{ + InsecureRegistries: tc.insecureRegistries, + }) + assert.NilError(t, err) + + sec := config.isSecureIndex(tc.addr) + assert.Equal(t, sec, tc.expected, "isSecureIndex failed for %q %v, expected %v got %v", tc.addr, tc.insecureRegistries, tc.expected, sec) + } +} diff --git a/vendor/github.com/docker/docker/registry/search.go b/internal/registry/search.go similarity index 100% rename from vendor/github.com/docker/docker/registry/search.go rename to internal/registry/search.go diff --git a/vendor/github.com/docker/docker/registry/search_endpoint_v1.go b/internal/registry/search_endpoint_v1.go similarity index 92% rename from vendor/github.com/docker/docker/registry/search_endpoint_v1.go rename to internal/registry/search_endpoint_v1.go index 2ac3cee8296d..2b20c6044ed9 100644 --- a/vendor/github.com/docker/docker/registry/search_endpoint_v1.go +++ b/internal/registry/search_endpoint_v1.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/json" "errors" + "fmt" "net/http" "net/url" "strings" @@ -58,7 +59,12 @@ func newV1Endpoint(ctx context.Context, index *registry.IndexInfo, headers http. if endpoint.IsSecure { // If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry` // in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fall back to HTTP. - return nil, invalidParamf("invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host) + hint := fmt.Sprintf( + ". If this private registry supports only HTTP or HTTPS with an unknown CA certificate, add `--insecure-registry %[1]s` to the daemon's arguments. "+ + "In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; place the CA certificate at /etc/docker/certs.d/%[1]s/ca.crt", + endpoint.URL.Host, + ) + return nil, invalidParamf("invalid registry endpoint %s: %v%s", endpoint, err, hint) } // registry is insecure and HTTPS failed, fallback to HTTP. @@ -163,9 +169,9 @@ func (e *v1Endpoint) ping(ctx context.Context) (v1PingResult, error) { // httpClient returns an HTTP client structure which uses the given transport // and contains the necessary headers for redirected requests -func httpClient(transport http.RoundTripper) *http.Client { +func httpClient(tr http.RoundTripper) *http.Client { return &http.Client{ - Transport: transport, + Transport: tr, CheckRedirect: addRequiredHeadersToRedirectedRequests, } } diff --git a/internal/registry/search_endpoint_v1_test.go b/internal/registry/search_endpoint_v1_test.go new file mode 100644 index 000000000000..a430f34cc1de --- /dev/null +++ b/internal/registry/search_endpoint_v1_test.go @@ -0,0 +1,237 @@ +package registry + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/docker/docker/api/types/registry" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestV1EndpointPing(t *testing.T) { + testPing := func(index *registry.IndexInfo, expectedStandalone bool, assertMessage string) { + ep, err := newV1Endpoint(context.Background(), index, nil) + if err != nil { + t.Fatal(err) + } + regInfo, err := ep.ping(context.Background()) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, regInfo.Standalone, expectedStandalone, assertMessage) + } + + testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)") + testPing(makeHTTPSIndex("/v1/"), true, "Expected standalone to be true (default)") + testPing(makePublicIndex(), false, "Expected standalone to be false for public index") +} + +func TestV1Endpoint(t *testing.T) { + // Simple wrapper to fail test if err != nil + expandEndpoint := func(index *registry.IndexInfo) *v1Endpoint { + endpoint, err := newV1Endpoint(context.Background(), index, nil) + if err != nil { + t.Fatal(err) + } + return endpoint + } + + assertInsecureIndex := func(index *registry.IndexInfo) { + index.Secure = true + _, err := newV1Endpoint(context.Background(), index, nil) + assert.ErrorContains(t, err, "insecure-registry", index.Name+": Expected insecure-registry error for insecure index") + index.Secure = false + } + + assertSecureIndex := func(index *registry.IndexInfo) { + index.Secure = true + _, err := newV1Endpoint(context.Background(), index, nil) + assert.ErrorContains(t, err, "certificate signed by unknown authority", index.Name+": Expected cert error for secure index") + index.Secure = false + } + + index := ®istry.IndexInfo{} + index.Name = makeURL("/v1/") + endpoint := expandEndpoint(index) + assert.Equal(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name) + assertInsecureIndex(index) + + index.Name = makeURL("") + endpoint = expandEndpoint(index) + assert.Equal(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/") + assertInsecureIndex(index) + + httpURL := makeURL("") + index.Name = strings.SplitN(httpURL, "://", 2)[1] + endpoint = expandEndpoint(index) + assert.Equal(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/") + assertInsecureIndex(index) + + index.Name = makeHTTPSURL("/v1/") + endpoint = expandEndpoint(index) + assert.Equal(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name) + assertSecureIndex(index) + + index.Name = makeHTTPSURL("") + endpoint = expandEndpoint(index) + assert.Equal(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/") + assertSecureIndex(index) + + httpsURL := makeHTTPSURL("") + index.Name = strings.SplitN(httpsURL, "://", 2)[1] + endpoint = expandEndpoint(index) + assert.Equal(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/") + assertSecureIndex(index) + + badEndpoints := []string{ + "http://127.0.0.1/v1/", + "https://127.0.0.1/v1/", + "http://127.0.0.1", + "https://127.0.0.1", + "127.0.0.1", + } + for _, address := range badEndpoints { + index.Name = address + _, err := newV1Endpoint(context.Background(), index, nil) + assert.Check(t, err != nil, "Expected error while expanding bad endpoint: %s", address) + } +} + +func TestV1EndpointParse(t *testing.T) { + tests := []struct { + address string + expected string + expectedErr string + }{ + { + address: IndexServer, + expected: IndexServer, + }, + { + address: "https://0.0.0.0:5000/v1/", + expected: "https://0.0.0.0:5000/v1/", + }, + { + address: "https://0.0.0.0:5000", + expected: "https://0.0.0.0:5000/v1/", + }, + { + address: "0.0.0.0:5000", + expected: "https://0.0.0.0:5000/v1/", + }, + { + address: "https://0.0.0.0:5000/nonversion/", + expected: "https://0.0.0.0:5000/nonversion/v1/", + }, + { + address: "https://0.0.0.0:5000/v0/", + expected: "https://0.0.0.0:5000/v0/v1/", + }, + { + address: "https://0.0.0.0:5000/v2/", + expectedErr: "search is not supported on v2 endpoints: https://0.0.0.0:5000/v2/", + }, + } + for _, tc := range tests { + t.Run(tc.address, func(t *testing.T) { + ep, err := newV1EndpointFromStr(tc.address, nil, nil) + if tc.expectedErr != "" { + assert.Check(t, is.Error(err, tc.expectedErr)) + assert.Check(t, is.Nil(ep)) + } else { + assert.NilError(t, err) + assert.Check(t, is.Equal(ep.String(), tc.expected)) + } + }) + } +} + +// Ensure that a registry endpoint that responds with a 401 only is determined +// to be a valid v1 registry endpoint +func TestV1EndpointValidate(t *testing.T) { + requireBasicAuthHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("WWW-Authenticate", `Basic realm="localhost"`) + w.WriteHeader(http.StatusUnauthorized) + }) + + // Make a test server which should validate as a v1 server. + testServer := httptest.NewServer(requireBasicAuthHandler) + defer testServer.Close() + + testEndpoint, err := newV1Endpoint(context.Background(), ®istry.IndexInfo{Name: testServer.URL}, nil) + if err != nil { + t.Fatal(err) + } + + if testEndpoint.URL.Scheme != "http" { + t.Fatalf("expecting to validate endpoint as http, got url %s", testEndpoint.String()) + } +} + +func TestTrustedLocation(t *testing.T) { + for _, u := range []string{"http://example.com", "https://example.com:7777", "http://docker.io", "http://test.docker.com", "https://fakedocker.com"} { + req, _ := http.NewRequest(http.MethodGet, u, http.NoBody) + assert.Check(t, !trustedLocation(req)) + } + + for _, u := range []string{"https://docker.io", "https://test.docker.com:80"} { + req, _ := http.NewRequest(http.MethodGet, u, http.NoBody) + assert.Check(t, trustedLocation(req)) + } +} + +func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) { + for _, urls := range [][]string{ + {"http://docker.io", "https://docker.com"}, + {"https://foo.docker.io:7777", "http://bar.docker.com"}, + {"https://foo.docker.io", "https://example.com"}, + } { + reqFrom, _ := http.NewRequest(http.MethodGet, urls[0], http.NoBody) + reqFrom.Header.Add("Content-Type", "application/json") + reqFrom.Header.Add("Authorization", "super_secret") + reqTo, _ := http.NewRequest(http.MethodGet, urls[1], http.NoBody) + + _ = addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) + + if len(reqTo.Header) != 1 { + t.Fatalf("Expected 1 headers, got %d", len(reqTo.Header)) + } + + if reqTo.Header.Get("Content-Type") != "application/json" { + t.Fatal("'Content-Type' should be 'application/json'") + } + + if reqTo.Header.Get("Authorization") != "" { + t.Fatal("'Authorization' should be empty") + } + } + + for _, urls := range [][]string{ + {"https://docker.io", "https://docker.com"}, + {"https://foo.docker.io:7777", "https://bar.docker.com"}, + } { + reqFrom, _ := http.NewRequest(http.MethodGet, urls[0], http.NoBody) + reqFrom.Header.Add("Content-Type", "application/json") + reqFrom.Header.Add("Authorization", "super_secret") + reqTo, _ := http.NewRequest(http.MethodGet, urls[1], http.NoBody) + + _ = addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) + + if len(reqTo.Header) != 2 { + t.Fatalf("Expected 2 headers, got %d", len(reqTo.Header)) + } + + if reqTo.Header.Get("Content-Type") != "application/json" { + t.Fatal("'Content-Type' should be 'application/json'") + } + + if reqTo.Header.Get("Authorization") != "super_secret" { + t.Fatal("'Authorization' should be 'super_secret'") + } + } +} diff --git a/vendor/github.com/docker/docker/registry/search_session.go b/internal/registry/search_session.go similarity index 98% rename from vendor/github.com/docker/docker/registry/search_session.go rename to internal/registry/search_session.go index f2886b7d3859..825777ac2c2b 100644 --- a/vendor/github.com/docker/docker/registry/search_session.go +++ b/internal/registry/search_session.go @@ -10,6 +10,7 @@ import ( "net/http" "net/http/cookiejar" "net/url" + "strconv" "strings" "sync" @@ -219,7 +220,7 @@ func (r *session) searchRepositories(ctx context.Context, term string, limit int if limit < 1 || limit > 100 { return nil, invalidParamf("limit %d is outside the range of [1, 100]", limit) } - u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(fmt.Sprintf("%d", limit)) + u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(strconv.Itoa(limit)) log.G(ctx).WithField("url", u).Debug("searchRepositories") req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, http.NoBody) @@ -236,7 +237,7 @@ func (r *session) searchRepositories(ctx context.Context, term string, limit int if res.StatusCode != http.StatusOK { // TODO(thaJeztah): return upstream response body for errors (see https://github.com/moby/moby/issues/27286). // TODO(thaJeztah): handle other status-codes to return correct error-type - return nil, errUnknown{fmt.Errorf("Unexpected status code %d", res.StatusCode)} + return nil, errUnknown{fmt.Errorf("unexpected status code %d", res.StatusCode)} } result := ®istry.SearchResults{} err = json.NewDecoder(res.Body).Decode(result) diff --git a/internal/registry/search_test.go b/internal/registry/search_test.go new file mode 100644 index 000000000000..d9f563df173e --- /dev/null +++ b/internal/registry/search_test.go @@ -0,0 +1,418 @@ +package registry + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "net/http/httputil" + "testing" + + cerrdefs "github.com/containerd/errdefs" + "github.com/docker/distribution/registry/client/transport" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/registry" + "gotest.tools/v3/assert" +) + +func spawnTestRegistrySession(t *testing.T) *session { + t.Helper() + authConfig := ®istry.AuthConfig{} + endpoint, err := newV1Endpoint(context.Background(), makeIndex("/v1/"), nil) + if err != nil { + t.Fatal(err) + } + userAgent := "docker test client" + var tr http.RoundTripper = debugTransport{newTransport(nil), t.Log} + tr = transport.NewTransport(newAuthTransport(tr, authConfig, false), Headers(userAgent, nil)...) + client := httpClient(tr) + + if err := authorizeClient(context.Background(), client, authConfig, endpoint); err != nil { + t.Fatal(err) + } + r := newSession(client, endpoint) + + // In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true` + // header while authenticating, in order to retrieve a token that can be later used to + // perform authenticated actions. + // + // The mock v1 registry does not support that, (TODO(tiborvass): support it), instead, + // it will consider authenticated any request with the header `X-Docker-Token: fake-token`. + // + // Because we know that the client's transport is an `*authTransport` we simply cast it, + // in order to set the internal cached token to the fake token, and thus send that fake token + // upon every subsequent requests. + r.client.Transport.(*authTransport).token = []string{"fake-token"} + return r +} + +type debugTransport struct { + http.RoundTripper + log func(...interface{}) +} + +func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) { + dump, err := httputil.DumpRequestOut(req, false) + if err != nil { + tr.log("could not dump request") + } + tr.log(string(dump)) + resp, err := tr.RoundTripper.RoundTrip(req) + if err != nil { + return nil, err + } + dump, err = httputil.DumpResponse(resp, false) + if err != nil { + tr.log("could not dump response") + } + tr.log(string(dump)) + return resp, err +} + +func TestSearchRepositories(t *testing.T) { + r := spawnTestRegistrySession(t) + results, err := r.searchRepositories(context.Background(), "fakequery", 25) + if err != nil { + t.Fatal(err) + } + if results == nil { + t.Fatal("Expected non-nil SearchResults object") + } + assert.Equal(t, results.NumResults, 1, "Expected 1 search results") + assert.Equal(t, results.Query, "fakequery", "Expected 'fakequery' as query") + assert.Equal(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars") +} + +func TestSearchErrors(t *testing.T) { + errorCases := []struct { + filtersArgs filters.Args + shouldReturnError bool + expectedError string + }{ + { + expectedError: "unexpected status code 500", + shouldReturnError: true, + }, + { + filtersArgs: filters.NewArgs(filters.Arg("type", "custom")), + expectedError: "invalid filter 'type'", + }, + { + filtersArgs: filters.NewArgs(filters.Arg("is-automated", "invalid")), + expectedError: "invalid filter 'is-automated=[invalid]'", + }, + { + filtersArgs: filters.NewArgs( + filters.Arg("is-automated", "true"), + filters.Arg("is-automated", "false"), + ), + expectedError: "invalid filter 'is-automated", + }, + { + filtersArgs: filters.NewArgs(filters.Arg("is-official", "invalid")), + expectedError: "invalid filter 'is-official=[invalid]'", + }, + { + filtersArgs: filters.NewArgs( + filters.Arg("is-official", "true"), + filters.Arg("is-official", "false"), + ), + expectedError: "invalid filter 'is-official", + }, + { + filtersArgs: filters.NewArgs(filters.Arg("stars", "invalid")), + expectedError: "invalid filter 'stars=invalid'", + }, + { + filtersArgs: filters.NewArgs( + filters.Arg("stars", "1"), + filters.Arg("stars", "invalid"), + ), + expectedError: "invalid filter 'stars=invalid'", + }, + } + for _, tc := range errorCases { + t.Run(tc.expectedError, func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !tc.shouldReturnError { + t.Errorf("unexpected HTTP request") + } + http.Error(w, "no search for you", http.StatusInternalServerError) + })) + defer srv.Close() + + // Construct the search term by cutting the 'http://' prefix off srv.URL. + term := srv.URL[7:] + "/term" + + reg, err := NewService(ServiceOptions{}) + assert.NilError(t, err) + _, err = reg.Search(context.Background(), tc.filtersArgs, term, 0, nil, map[string][]string{}) + assert.ErrorContains(t, err, tc.expectedError) + if tc.shouldReturnError { + assert.Check(t, cerrdefs.IsUnknown(err), "got: %T: %v", err, err) + return + } + assert.Check(t, cerrdefs.IsInvalidArgument(err), "got: %T: %v", err, err) + }) + } +} + +func TestSearch(t *testing.T) { + const term = "term" + successCases := []struct { + name string + filtersArgs filters.Args + registryResults []registry.SearchResult + expectedResults []registry.SearchResult + }{ + { + name: "empty results", + registryResults: []registry.SearchResult{}, + expectedResults: []registry.SearchResult{}, + }, + { + name: "no filter", + registryResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + }, + }, + expectedResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + }, + }, + }, + { + name: "is-automated=true, no results", + filtersArgs: filters.NewArgs(filters.Arg("is-automated", "true")), + registryResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + }, + }, + expectedResults: []registry.SearchResult{}, + }, + { + name: "is-automated=true", + filtersArgs: filters.NewArgs(filters.Arg("is-automated", "true")), + registryResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated). + }, + }, + expectedResults: []registry.SearchResult{}, + }, + { + name: "is-automated=false, IsAutomated reset to false", + filtersArgs: filters.NewArgs(filters.Arg("is-automated", "false")), + registryResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated). + }, + }, + expectedResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + IsAutomated: false, //nolint:staticcheck // ignore SA1019 (field is deprecated). + }, + }, + }, + { + name: "is-automated=false", + filtersArgs: filters.NewArgs(filters.Arg("is-automated", "false")), + registryResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + }, + }, + expectedResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + }, + }, + }, + { + name: "is-official=true, no results", + filtersArgs: filters.NewArgs(filters.Arg("is-official", "true")), + registryResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + }, + }, + expectedResults: []registry.SearchResult{}, + }, + { + name: "is-official=true", + filtersArgs: filters.NewArgs(filters.Arg("is-official", "true")), + registryResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + IsOfficial: true, + }, + }, + expectedResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + IsOfficial: true, + }, + }, + }, + { + name: "is-official=false, no results", + filtersArgs: filters.NewArgs(filters.Arg("is-official", "false")), + registryResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + IsOfficial: true, + }, + }, + expectedResults: []registry.SearchResult{}, + }, + { + name: "is-official=false", + filtersArgs: filters.NewArgs(filters.Arg("is-official", "false")), + registryResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + IsOfficial: false, + }, + }, + expectedResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + IsOfficial: false, + }, + }, + }, + { + name: "stars=0", + filtersArgs: filters.NewArgs(filters.Arg("stars", "0")), + registryResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + StarCount: 0, + }, + }, + expectedResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + StarCount: 0, + }, + }, + }, + { + name: "stars=0, no results", + filtersArgs: filters.NewArgs(filters.Arg("stars", "1")), + registryResults: []registry.SearchResult{ + { + Name: "name", + Description: "description", + StarCount: 0, + }, + }, + expectedResults: []registry.SearchResult{}, + }, + { + name: "stars=1", + filtersArgs: filters.NewArgs(filters.Arg("stars", "1")), + registryResults: []registry.SearchResult{ + { + Name: "name0", + Description: "description0", + StarCount: 0, + }, + { + Name: "name1", + Description: "description1", + StarCount: 1, + }, + }, + expectedResults: []registry.SearchResult{ + { + Name: "name1", + Description: "description1", + StarCount: 1, + }, + }, + }, + { + name: "stars=1, is-official=true, is-automated=true", + filtersArgs: filters.NewArgs( + filters.Arg("stars", "1"), + filters.Arg("is-official", "true"), + filters.Arg("is-automated", "true"), + ), + registryResults: []registry.SearchResult{ + { + Name: "name0", + Description: "description0", + StarCount: 0, + IsOfficial: true, + IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated). + }, + { + Name: "name1", + Description: "description1", + StarCount: 1, + IsOfficial: false, + IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated). + }, + { + Name: "name2", + Description: "description2", + StarCount: 1, + IsOfficial: true, + }, + { + Name: "name3", + Description: "description3", + StarCount: 2, + IsOfficial: true, + IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated). + }, + }, + expectedResults: []registry.SearchResult{}, + }, + } + for _, tc := range successCases { + t.Run(tc.name, func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-type", "application/json") + json.NewEncoder(w).Encode(registry.SearchResults{ + Query: term, + NumResults: len(tc.registryResults), + Results: tc.registryResults, + }) + })) + defer srv.Close() + + // Construct the search term by cutting the 'http://' prefix off srv.URL. + searchTerm := srv.URL[7:] + "/" + term + + reg, err := NewService(ServiceOptions{}) + assert.NilError(t, err) + results, err := reg.Search(context.Background(), tc.filtersArgs, searchTerm, 0, nil, map[string][]string{}) + assert.NilError(t, err) + assert.DeepEqual(t, results, tc.expectedResults) + }) + } +} diff --git a/vendor/github.com/docker/docker/registry/service.go b/internal/registry/service.go similarity index 82% rename from vendor/github.com/docker/docker/registry/service.go rename to internal/registry/service.go index 85299be32ec1..73c9edfcd102 100644 --- a/vendor/github.com/docker/docker/registry/service.go +++ b/internal/registry/service.go @@ -108,17 +108,6 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use return "", "", lastErr } -// ResolveRepository splits a repository name into its components -// and configuration of the associated registry. -// -// Deprecated: this function was only used internally and is no longer used. It will be removed in the next release. -func (s *Service) ResolveRepository(name reference.Named) (*RepositoryInfo, error) { - s.mu.RLock() - defer s.mu.RUnlock() - // TODO(thaJeztah): remove error return as it's no longer used. - return newRepositoryInfo(s.config, name), nil -} - // ResolveAuthConfig looks up authentication for the given reference from the // given authConfigs. // @@ -139,12 +128,9 @@ func (s *Service) ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, // APIEndpoint represents a remote API endpoint type APIEndpoint struct { - Mirror bool - URL *url.URL - AllowNondistributableArtifacts bool // Deprecated: non-distributable artifacts are deprecated and enabled by default. This field will be removed in the next release. - Official bool // Deprecated: this field was only used internally, and will be removed in the next release. - TrimHostname bool // Deprecated: hostname is now trimmed unconditionally for remote names. This field will be removed in the next release. - TLSConfig *tls.Config + Mirror bool + URL *url.URL + TLSConfig *tls.Config } // LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference. diff --git a/vendor/github.com/docker/docker/registry/service_v2.go b/internal/registry/service_v2.go similarity index 98% rename from vendor/github.com/docker/docker/registry/service_v2.go rename to internal/registry/service_v2.go index 6b25a41dc327..df343a155dec 100644 --- a/vendor/github.com/docker/docker/registry/service_v2.go +++ b/internal/registry/service_v2.go @@ -37,7 +37,6 @@ func (s *Service) lookupV2Endpoints(ctx context.Context, hostname string, includ } endpoints = append(endpoints, APIEndpoint{ URL: DefaultV2Registry, - Official: true, TLSConfig: tlsconfig.ServerDefault(), }) diff --git a/internal/registry/types.go b/internal/registry/types.go new file mode 100644 index 000000000000..c35eb9fad2a8 --- /dev/null +++ b/internal/registry/types.go @@ -0,0 +1,13 @@ +package registry + +import ( + "github.com/distribution/reference" + "github.com/docker/docker/api/types/registry" +) + +// RepositoryInfo describes a repository +type RepositoryInfo struct { + Name reference.Named + // Index points to registry information + Index *registry.IndexInfo +} diff --git a/vendor.mod b/vendor.mod index f971da50d4c6..b0744d0334e8 100644 --- a/vendor.mod +++ b/vendor.mod @@ -9,6 +9,7 @@ go 1.23.0 require ( dario.cat/mergo v1.0.1 github.com/containerd/errdefs v1.0.0 + github.com/containerd/log v0.1.0 github.com/containerd/platforms v1.0.0-rc.1 github.com/cpuguy83/go-md2man/v2 v2.0.7 github.com/creack/pty v1.1.24 @@ -47,6 +48,7 @@ require ( github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346 github.com/xeipuuv/gojsonschema v1.2.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 go.opentelemetry.io/otel v1.35.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 @@ -70,7 +72,6 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/containerd/log v0.1.0 // indirect github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect @@ -97,7 +98,6 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect go.etcd.io/etcd/raft/v3 v3.5.16 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir.go b/vendor/github.com/docker/docker/pkg/homedir/homedir.go deleted file mode 100644 index c0ab3f5bf359..000000000000 --- a/vendor/github.com/docker/docker/pkg/homedir/homedir.go +++ /dev/null @@ -1,28 +0,0 @@ -package homedir - -import ( - "os" - "os/user" - "runtime" -) - -// Get returns the home directory of the current user with the help of -// environment variables depending on the target operating system. -// Returned path should be used with "path/filepath" to form new paths. -// -// On non-Windows platforms, it falls back to nss lookups, if the home -// directory cannot be obtained from environment-variables. -// -// If linking statically with cgo enabled against glibc, ensure the -// osusergo build tag is used. -// -// If needing to do nss lookups, do not disable cgo or set osusergo. -func Get() string { - home, _ := os.UserHomeDir() - if home == "" && runtime.GOOS != "windows" { - if u, err := user.Current(); err == nil { - return u.HomeDir - } - } - return home -} diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go b/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go deleted file mode 100644 index 469395f16e2e..000000000000 --- a/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go +++ /dev/null @@ -1,105 +0,0 @@ -package homedir - -import ( - "errors" - "os" - "path/filepath" - "strings" -) - -// GetRuntimeDir returns XDG_RUNTIME_DIR. -// XDG_RUNTIME_DIR is typically configured via pam_systemd. -// GetRuntimeDir returns non-nil error if XDG_RUNTIME_DIR is not set. -// -// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html -func GetRuntimeDir() (string, error) { - if xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR"); xdgRuntimeDir != "" { - return xdgRuntimeDir, nil - } - return "", errors.New("could not get XDG_RUNTIME_DIR") -} - -// StickRuntimeDirContents sets the sticky bit on files that are under -// XDG_RUNTIME_DIR, so that the files won't be periodically removed by the system. -// -// StickyRuntimeDir returns slice of sticked files. -// StickyRuntimeDir returns nil error if XDG_RUNTIME_DIR is not set. -// -// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html -func StickRuntimeDirContents(files []string) ([]string, error) { - runtimeDir, err := GetRuntimeDir() - if err != nil { - // ignore error if runtimeDir is empty - return nil, nil - } - runtimeDir, err = filepath.Abs(runtimeDir) - if err != nil { - return nil, err - } - var sticked []string - for _, f := range files { - f, err = filepath.Abs(f) - if err != nil { - return sticked, err - } - if strings.HasPrefix(f, runtimeDir+"/") { - if err = stick(f); err != nil { - return sticked, err - } - sticked = append(sticked, f) - } - } - return sticked, nil -} - -func stick(f string) error { - st, err := os.Stat(f) - if err != nil { - return err - } - m := st.Mode() - m |= os.ModeSticky - return os.Chmod(f, m) -} - -// GetDataHome returns XDG_DATA_HOME. -// GetDataHome returns $HOME/.local/share and nil error if XDG_DATA_HOME is not set. -// If HOME and XDG_DATA_HOME are not set, getpwent(3) is consulted to determine the users home directory. -// -// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html -func GetDataHome() (string, error) { - if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" { - return xdgDataHome, nil - } - home := Get() - if home == "" { - return "", errors.New("could not get either XDG_DATA_HOME or HOME") - } - return filepath.Join(home, ".local", "share"), nil -} - -// GetConfigHome returns XDG_CONFIG_HOME. -// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set. -// If HOME and XDG_CONFIG_HOME are not set, getpwent(3) is consulted to determine the users home directory. -// -// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html -func GetConfigHome() (string, error) { - if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" { - return xdgConfigHome, nil - } - home := Get() - if home == "" { - return "", errors.New("could not get either XDG_CONFIG_HOME or HOME") - } - return filepath.Join(home, ".config"), nil -} - -// GetLibHome returns $HOME/.local/lib -// If HOME is not set, getpwent(3) is consulted to determine the users home directory. -func GetLibHome() (string, error) { - home := Get() - if home == "" { - return "", errors.New("could not get HOME") - } - return filepath.Join(home, ".local/lib"), nil -} diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go b/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go deleted file mode 100644 index 1e41e6aab51f..000000000000 --- a/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build !linux - -package homedir - -import ( - "errors" -) - -// GetRuntimeDir is unsupported on non-linux system. -func GetRuntimeDir() (string, error) { - return "", errors.New("homedir.GetRuntimeDir() is not supported on this system") -} - -// StickRuntimeDirContents is unsupported on non-linux system. -func StickRuntimeDirContents(files []string) ([]string, error) { - return nil, errors.New("homedir.StickRuntimeDirContents() is not supported on this system") -} - -// GetDataHome is unsupported on non-linux system. -func GetDataHome() (string, error) { - return "", errors.New("homedir.GetDataHome() is not supported on this system") -} - -// GetConfigHome is unsupported on non-linux system. -func GetConfigHome() (string, error) { - return "", errors.New("homedir.GetConfigHome() is not supported on this system") -} - -// GetLibHome is unsupported on non-linux system. -func GetLibHome() (string, error) { - return "", errors.New("homedir.GetLibHome() is not supported on this system") -} diff --git a/vendor/github.com/docker/docker/registry/config_unix.go b/vendor/github.com/docker/docker/registry/config_unix.go deleted file mode 100644 index 6aa6cdcca391..000000000000 --- a/vendor/github.com/docker/docker/registry/config_unix.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !windows - -package registry - -// defaultCertsDir is the platform-specific default directory where certificates -// are stored. On Linux, it may be overridden through certsDir, for example, when -// running in rootless mode. -const defaultCertsDir = "/etc/docker/certs.d" - -// cleanPath is used to ensure that a directory name is valid on the target -// platform. It will be passed in something *similar* to a URL such as -// https:/index.docker.io/v1. Not all platforms support directory names -// which contain those characters (such as : on Windows) -func cleanPath(s string) string { - return s -} diff --git a/vendor/github.com/docker/docker/registry/config_windows.go b/vendor/github.com/docker/docker/registry/config_windows.go deleted file mode 100644 index fd13bffde0f0..000000000000 --- a/vendor/github.com/docker/docker/registry/config_windows.go +++ /dev/null @@ -1,20 +0,0 @@ -package registry - -import ( - "os" - "path/filepath" - "strings" -) - -// defaultCertsDir is the platform-specific default directory where certificates -// are stored. On Linux, it may be overridden through certsDir, for example, when -// running in rootless mode. -var defaultCertsDir = os.Getenv("programdata") + `\docker\certs.d` - -// cleanPath is used to ensure that a directory name is valid on the target -// platform. It will be passed in something *similar* to a URL such as -// https:\index.docker.io\v1. Not all platforms support directory names -// which contain those characters (such as : on Windows) -func cleanPath(s string) string { - return filepath.FromSlash(strings.ReplaceAll(s, ":", "")) -} diff --git a/vendor/github.com/docker/docker/registry/types.go b/vendor/github.com/docker/docker/registry/types.go deleted file mode 100644 index bb081d5638a2..000000000000 --- a/vendor/github.com/docker/docker/registry/types.go +++ /dev/null @@ -1,24 +0,0 @@ -package registry - -import ( - "github.com/distribution/reference" - "github.com/docker/docker/api/types/registry" -) - -// RepositoryInfo describes a repository -type RepositoryInfo struct { - Name reference.Named - // Index points to registry information - Index *registry.IndexInfo - // Official indicates whether the repository is considered official. - // If the registry is official, and the normalized name does not - // contain a '/' (e.g. "foo"), then it is considered an official repo. - // - // Deprecated: this field is no longer used and will be removed in the next release. The information captured in this field can be obtained from the [Name] field instead. - Official bool - // Class represents the class of the repository, such as "plugin" - // or "image". - // - // Deprecated: this field is no longer used, and will be removed in the next release. - Class string -} diff --git a/vendor/modules.txt b/vendor/modules.txt index cebbc521b4f5..8ccf406adc1f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -92,12 +92,10 @@ github.com/docker/docker/api/types/volume github.com/docker/docker/client github.com/docker/docker/internal/lazyregexp github.com/docker/docker/internal/multierror -github.com/docker/docker/pkg/homedir github.com/docker/docker/pkg/jsonmessage github.com/docker/docker/pkg/progress github.com/docker/docker/pkg/stdcopy github.com/docker/docker/pkg/streamformatter -github.com/docker/docker/registry # github.com/docker/docker-credential-helpers v0.9.3 ## explicit; go 1.21 github.com/docker/docker-credential-helpers/client From ecd54bc6dd10e8569e91d804520003497b3fe362 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 24 Jul 2025 01:36:57 +0200 Subject: [PATCH 014/193] internal/registry: remove dead code Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 7716219e17d935a6327e244333c1deeddc3574aa) Signed-off-by: Sebastiaan van Stijn --- internal/registry/auth.go | 38 -- internal/registry/auth_test.go | 106 ----- internal/registry/config.go | 45 -- internal/registry/errors.go | 16 - internal/registry/registry_mock_test.go | 30 +- internal/registry/registry_test.go | 17 + internal/registry/search.go | 170 -------- internal/registry/search_endpoint_v1.go | 213 ---------- internal/registry/search_endpoint_v1_test.go | 237 ----------- internal/registry/search_session.go | 248 ----------- internal/registry/search_test.go | 418 ------------------- internal/registry/service.go | 58 --- 12 files changed, 18 insertions(+), 1578 deletions(-) delete mode 100644 internal/registry/auth_test.go delete mode 100644 internal/registry/search.go delete mode 100644 internal/registry/search_endpoint_v1.go delete mode 100644 internal/registry/search_endpoint_v1_test.go delete mode 100644 internal/registry/search_session.go delete mode 100644 internal/registry/search_test.go diff --git a/internal/registry/auth.go b/internal/registry/auth.go index f99c58a12141..8799686531aa 100644 --- a/internal/registry/auth.go +++ b/internal/registry/auth.go @@ -127,44 +127,6 @@ func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifi }, nil } -// ConvertToHostname normalizes a registry URL which has http|https prepended -// to just its hostname. It is used to match credentials, which may be either -// stored as hostname or as hostname including scheme (in legacy configuration -// files). -func ConvertToHostname(maybeURL string) string { - stripped := maybeURL - if scheme, remainder, ok := strings.Cut(stripped, "://"); ok { - switch scheme { - case "http", "https": - stripped = remainder - default: - // unknown, or no scheme; doing nothing for now, as we never did. - } - } - stripped, _, _ = strings.Cut(stripped, "/") - return stripped -} - -// ResolveAuthConfig matches an auth configuration to a server address or a URL -func ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, index *registry.IndexInfo) registry.AuthConfig { - configKey := GetAuthConfigKey(index) - // First try the happy case - if c, found := authConfigs[configKey]; found || index.Official { - return c - } - - // Maybe they have a legacy config file, we will iterate the keys converting - // them to the new format and testing - for registryURL, ac := range authConfigs { - if configKey == ConvertToHostname(registryURL) { - return ac - } - } - - // When all else fails, return an empty auth config - return registry.AuthConfig{} -} - // PingResponseError is used when the response from a ping // was received but invalid. type PingResponseError struct { diff --git a/internal/registry/auth_test.go b/internal/registry/auth_test.go deleted file mode 100644 index 927254177d3b..000000000000 --- a/internal/registry/auth_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package registry - -import ( - "testing" - - "github.com/docker/docker/api/types/registry" - "gotest.tools/v3/assert" -) - -func buildAuthConfigs() map[string]registry.AuthConfig { - authConfigs := map[string]registry.AuthConfig{} - - for _, reg := range []string{"testIndex", IndexServer} { - authConfigs[reg] = registry.AuthConfig{ - Username: "docker-user", - Password: "docker-pass", - } - } - - return authConfigs -} - -func TestResolveAuthConfigIndexServer(t *testing.T) { - authConfigs := buildAuthConfigs() - indexConfig := authConfigs[IndexServer] - - officialIndex := ®istry.IndexInfo{ - Official: true, - } - privateIndex := ®istry.IndexInfo{ - Official: false, - } - - resolved := ResolveAuthConfig(authConfigs, officialIndex) - assert.Equal(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServer") - - resolved = ResolveAuthConfig(authConfigs, privateIndex) - assert.Check(t, resolved != indexConfig, "Expected ResolveAuthConfig to not return IndexServer") -} - -func TestResolveAuthConfigFullURL(t *testing.T) { - authConfigs := buildAuthConfigs() - - registryAuth := registry.AuthConfig{ - Username: "foo-user", - Password: "foo-pass", - } - localAuth := registry.AuthConfig{ - Username: "bar-user", - Password: "bar-pass", - } - officialAuth := registry.AuthConfig{ - Username: "baz-user", - Password: "baz-pass", - } - authConfigs[IndexServer] = officialAuth - - expectedAuths := map[string]registry.AuthConfig{ - "registry.example.com": registryAuth, - "localhost:8000": localAuth, - "example.com": localAuth, - } - - validRegistries := map[string][]string{ - "registry.example.com": { - "https://registry.example.com/v1/", - "http://registry.example.com/v1/", - "registry.example.com", - "registry.example.com/v1/", - }, - "localhost:8000": { - "https://localhost:8000/v1/", - "http://localhost:8000/v1/", - "localhost:8000", - "localhost:8000/v1/", - }, - "example.com": { - "https://example.com/v1/", - "http://example.com/v1/", - "example.com", - "example.com/v1/", - }, - } - - for configKey, registries := range validRegistries { - configured, ok := expectedAuths[configKey] - if !ok { - t.Fail() - } - index := ®istry.IndexInfo{ - Name: configKey, - } - for _, reg := range registries { - authConfigs[reg] = configured - resolved := ResolveAuthConfig(authConfigs, index) - if resolved.Username != configured.Username || resolved.Password != configured.Password { - t.Errorf("%s -> %v != %v\n", reg, resolved, configured) - } - delete(authConfigs, reg) - resolved = ResolveAuthConfig(authConfigs, index) - if resolved.Username == configured.Username || resolved.Password == configured.Password { - t.Errorf("%s -> %v == %v\n", reg, resolved, configured) - } - } - } -} diff --git a/internal/registry/config.go b/internal/registry/config.go index 1aa466147cd0..dac5e7fd6c11 100644 --- a/internal/registry/config.go +++ b/internal/registry/config.go @@ -106,19 +106,6 @@ func newServiceConfig(options ServiceOptions) (*serviceConfig, error) { return config, nil } -// copy constructs a new ServiceConfig with a copy of the configuration in config. -func (config *serviceConfig) copy() *registry.ServiceConfig { - ic := make(map[string]*registry.IndexInfo) - for key, value := range config.IndexConfigs { - ic[key] = value - } - return ®istry.ServiceConfig{ - InsecureRegistryCIDRs: append([]*registry.NetIPNet(nil), config.InsecureRegistryCIDRs...), - IndexConfigs: ic, - Mirrors: append([]string(nil), config.Mirrors...), - } -} - // loadMirrors loads mirrors to config, after removing duplicates. // Returns an error if mirrors contains an invalid mirror. func (config *serviceConfig) loadMirrors(mirrors []string) error { @@ -320,18 +307,12 @@ func ValidateIndexName(val string) (string, error) { } func normalizeIndexName(val string) string { - // TODO(thaJeztah): consider normalizing other known options, such as "(https://)registry-1.docker.io", "https://index.docker.io/v1/". - // TODO: upstream this to check to reference package if val == "index.docker.io" { return "docker.io" } return val } -func hasScheme(reposName string) bool { - return strings.Contains(reposName, "://") -} - func validateHostPort(s string) error { // Split host and port, and in case s can not be split, assume host only host, port, err := net.SplitHostPort(s) @@ -356,32 +337,6 @@ func validateHostPort(s string) error { return nil } -// newIndexInfo returns IndexInfo configuration from indexName -func newIndexInfo(config *serviceConfig, indexName string) *registry.IndexInfo { - indexName = normalizeIndexName(indexName) - - // Return any configured index info, first. - if index, ok := config.IndexConfigs[indexName]; ok { - return index - } - - // Construct a non-configured index info. - return ®istry.IndexInfo{ - Name: indexName, - Mirrors: []string{}, - Secure: config.isSecureIndex(indexName), - } -} - -// GetAuthConfigKey special-cases using the full index address of the official -// index as the AuthConfig key, and uses the (host)name[:port] for private indexes. -func GetAuthConfigKey(index *registry.IndexInfo) string { - if index.Official { - return IndexServer - } - return index.Name -} - // ParseRepositoryInfo performs the breakdown of a repository name into a // [RepositoryInfo], but lacks registry configuration. // diff --git a/internal/registry/errors.go b/internal/registry/errors.go index d37155a789de..4174c91d15b4 100644 --- a/internal/registry/errors.go +++ b/internal/registry/errors.go @@ -49,19 +49,3 @@ func (invalidParameterErr) InvalidParameter() {} func (e invalidParameterErr) Unwrap() error { return e.error } - -type systemErr struct{ error } - -func (systemErr) System() {} - -func (e systemErr) Unwrap() error { - return e.error -} - -type errUnknown struct{ error } - -func (errUnknown) Unknown() {} - -func (e errUnknown) Unwrap() error { - return e.error -} diff --git a/internal/registry/registry_mock_test.go b/internal/registry/registry_mock_test.go index eb6b16de790d..6ccabce5bcaf 100644 --- a/internal/registry/registry_mock_test.go +++ b/internal/registry/registry_mock_test.go @@ -13,10 +13,7 @@ import ( "gotest.tools/v3/assert" ) -var ( - testHTTPServer *httptest.Server - testHTTPSServer *httptest.Server -) +var testHTTPServer *httptest.Server func init() { r := http.NewServeMux() @@ -29,7 +26,6 @@ func init() { r.HandleFunc("/v2/version", handlerGetPing) testHTTPServer = httptest.NewServer(handlerAccessLog(r)) - testHTTPSServer = httptest.NewTLSServer(handlerAccessLog(r)) } func handlerAccessLog(handler http.Handler) http.Handler { @@ -44,30 +40,6 @@ func makeURL(req string) string { return testHTTPServer.URL + req } -func makeHTTPSURL(req string) string { - return testHTTPSServer.URL + req -} - -func makeIndex(req string) *registry.IndexInfo { - return ®istry.IndexInfo{ - Name: makeURL(req), - } -} - -func makeHTTPSIndex(req string) *registry.IndexInfo { - return ®istry.IndexInfo{ - Name: makeHTTPSURL(req), - } -} - -func makePublicIndex() *registry.IndexInfo { - return ®istry.IndexInfo{ - Name: IndexServer, - Secure: true, - Official: true, - } -} - func writeHeaders(w http.ResponseWriter) { h := w.Header() h.Add("Server", "docker-tests/mock") diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go index 8495174c36b2..94d05eb4722c 100644 --- a/internal/registry/registry_test.go +++ b/internal/registry/registry_test.go @@ -34,6 +34,23 @@ func overrideLookupIP(t *testing.T) { }) } +// newIndexInfo returns IndexInfo configuration from indexName +func newIndexInfo(config *serviceConfig, indexName string) *registry.IndexInfo { + indexName = normalizeIndexName(indexName) + + // Return any configured index info, first. + if index, ok := config.IndexConfigs[indexName]; ok { + return index + } + + // Construct a non-configured index info. + return ®istry.IndexInfo{ + Name: indexName, + Mirrors: []string{}, + Secure: config.isSecureIndex(indexName), + } +} + func TestParseRepositoryInfo(t *testing.T) { type staticRepositoryInfo struct { Index *registry.IndexInfo diff --git a/internal/registry/search.go b/internal/registry/search.go deleted file mode 100644 index 26a14298ac6c..000000000000 --- a/internal/registry/search.go +++ /dev/null @@ -1,170 +0,0 @@ -package registry - -import ( - "context" - "net/http" - "strconv" - "strings" - - "github.com/containerd/log" - "github.com/docker/distribution/registry/client/auth" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/registry" - "github.com/pkg/errors" -) - -var acceptedSearchFilterTags = map[string]bool{ - "is-automated": true, // Deprecated: the "is_automated" field is deprecated and will always be false in the future. - "is-official": true, - "stars": true, -} - -// Search queries the public registry for repositories matching the specified -// search term and filters. -func (s *Service) Search(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, headers map[string][]string) ([]registry.SearchResult, error) { - if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil { - return nil, err - } - - isAutomated, err := searchFilters.GetBoolOrDefault("is-automated", false) - if err != nil { - return nil, err - } - - // "is-automated" is deprecated and filtering for `true` will yield no results. - if isAutomated { - return []registry.SearchResult{}, nil - } - - isOfficial, err := searchFilters.GetBoolOrDefault("is-official", false) - if err != nil { - return nil, err - } - - hasStarFilter := 0 - if searchFilters.Contains("stars") { - hasStars := searchFilters.Get("stars") - for _, hasStar := range hasStars { - iHasStar, err := strconv.Atoi(hasStar) - if err != nil { - return nil, invalidParameterErr{errors.Wrapf(err, "invalid filter 'stars=%s'", hasStar)} - } - if iHasStar > hasStarFilter { - hasStarFilter = iHasStar - } - } - } - - unfilteredResult, err := s.searchUnfiltered(ctx, term, limit, authConfig, headers) - if err != nil { - return nil, err - } - - filteredResults := []registry.SearchResult{} - for _, result := range unfilteredResult.Results { - if searchFilters.Contains("is-official") { - if isOfficial != result.IsOfficial { - continue - } - } - if searchFilters.Contains("stars") { - if result.StarCount < hasStarFilter { - continue - } - } - // "is-automated" is deprecated and the value in Docker Hub search - // results is untrustworthy. Force it to false so as to not mislead our - // clients. - result.IsAutomated = false //nolint:staticcheck // ignore SA1019 (field is deprecated) - filteredResults = append(filteredResults, result) - } - - return filteredResults, nil -} - -func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int, authConfig *registry.AuthConfig, headers http.Header) (*registry.SearchResults, error) { - if hasScheme(term) { - return nil, invalidParamf("invalid repository name: repository name (%s) should not have a scheme", term) - } - - indexName, remoteName := splitReposSearchTerm(term) - - // Search is a long-running operation, just lock s.config to avoid block others. - s.mu.RLock() - index := newIndexInfo(s.config, indexName) - s.mu.RUnlock() - if index.Official { - // If pull "library/foo", it's stored locally under "foo" - remoteName = strings.TrimPrefix(remoteName, "library/") - } - - endpoint, err := newV1Endpoint(ctx, index, headers) - if err != nil { - return nil, err - } - - var client *http.Client - if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" { - creds := NewStaticCredentialStore(authConfig) - - // TODO(thaJeztah); is there a reason not to include other headers here? (originally added in 19d48f0b8ba59eea9f2cac4ad1c7977712a6b7ac) - modifiers := Headers(headers.Get("User-Agent"), nil) - v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, []auth.Scope{ - auth.RegistryScope{Name: "catalog", Actions: []string{"search"}}, - }) - if err != nil { - return nil, err - } - // Copy non transport http client features - v2Client.Timeout = endpoint.client.Timeout - v2Client.CheckRedirect = endpoint.client.CheckRedirect - v2Client.Jar = endpoint.client.Jar - - log.G(ctx).Debugf("using v2 client for search to %s", endpoint.URL) - client = v2Client - } else { - client = endpoint.client - if err := authorizeClient(ctx, client, authConfig, endpoint); err != nil { - return nil, err - } - } - - return newSession(client, endpoint).searchRepositories(ctx, remoteName, limit) -} - -// splitReposSearchTerm breaks a search term into an index name and remote name -func splitReposSearchTerm(reposName string) (string, string) { - nameParts := strings.SplitN(reposName, "/", 2) - if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && - !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") { - // This is a Docker Hub repository (ex: samalba/hipache or ubuntu), - // use the default Docker Hub registry (docker.io) - return IndexName, reposName - } - return nameParts[0], nameParts[1] -} - -// ParseSearchIndexInfo will use repository name to get back an indexInfo. -// -// TODO(thaJeztah) this function is only used by the CLI, and used to get -// information of the registry (to provide credentials if needed). We should -// move this function (or equivalent) to the CLI, as it's doing too much just -// for that. -func ParseSearchIndexInfo(reposName string) (*registry.IndexInfo, error) { - indexName, _ := splitReposSearchTerm(reposName) - indexName = normalizeIndexName(indexName) - if indexName == IndexName { - return ®istry.IndexInfo{ - Name: IndexName, - Mirrors: []string{}, - Secure: true, - Official: true, - }, nil - } - - return ®istry.IndexInfo{ - Name: indexName, - Mirrors: []string{}, - Secure: !isInsecure(indexName), - }, nil -} diff --git a/internal/registry/search_endpoint_v1.go b/internal/registry/search_endpoint_v1.go deleted file mode 100644 index 2b20c6044ed9..000000000000 --- a/internal/registry/search_endpoint_v1.go +++ /dev/null @@ -1,213 +0,0 @@ -package registry - -import ( - "context" - "crypto/tls" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/containerd/log" - "github.com/docker/distribution/registry/client/transport" - "github.com/docker/docker/api/types/registry" -) - -// v1PingResult contains the information returned when pinging a registry. It -// indicates whether the registry claims to be a standalone registry. -type v1PingResult struct { - // Standalone is set to true if the registry indicates it is a - // standalone registry in the X-Docker-Registry-Standalone - // header - Standalone bool `json:"standalone"` -} - -// v1Endpoint stores basic information about a V1 registry endpoint. -type v1Endpoint struct { - client *http.Client - URL *url.URL - IsSecure bool -} - -// newV1Endpoint parses the given address to return a registry endpoint. -// TODO: remove. This is only used by search. -func newV1Endpoint(ctx context.Context, index *registry.IndexInfo, headers http.Header) (*v1Endpoint, error) { - tlsConfig, err := newTLSConfig(ctx, index.Name, index.Secure) - if err != nil { - return nil, err - } - - endpoint, err := newV1EndpointFromStr(GetAuthConfigKey(index), tlsConfig, headers) - if err != nil { - return nil, err - } - - if endpoint.String() == IndexServer { - // Skip the check, we know this one is valid - // (and we never want to fall back to http in case of error) - return endpoint, nil - } - - // Try HTTPS ping to registry - endpoint.URL.Scheme = "https" - if _, err := endpoint.ping(ctx); err != nil { - if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - return nil, err - } - if endpoint.IsSecure { - // If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry` - // in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fall back to HTTP. - hint := fmt.Sprintf( - ". If this private registry supports only HTTP or HTTPS with an unknown CA certificate, add `--insecure-registry %[1]s` to the daemon's arguments. "+ - "In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; place the CA certificate at /etc/docker/certs.d/%[1]s/ca.crt", - endpoint.URL.Host, - ) - return nil, invalidParamf("invalid registry endpoint %s: %v%s", endpoint, err, hint) - } - - // registry is insecure and HTTPS failed, fallback to HTTP. - log.G(ctx).WithError(err).Debugf("error from registry %q marked as insecure - insecurely falling back to HTTP", endpoint) - endpoint.URL.Scheme = "http" - if _, err2 := endpoint.ping(ctx); err2 != nil { - return nil, invalidParamf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2) - } - } - - return endpoint, nil -} - -// trimV1Address trims the "v1" version suffix off the address and returns -// the trimmed address. It returns an error on "v2" endpoints. -func trimV1Address(address string) (string, error) { - trimmed := strings.TrimSuffix(address, "/") - if strings.HasSuffix(trimmed, "/v2") { - return "", invalidParamf("search is not supported on v2 endpoints: %s", address) - } - return strings.TrimSuffix(trimmed, "/v1"), nil -} - -func newV1EndpointFromStr(address string, tlsConfig *tls.Config, headers http.Header) (*v1Endpoint, error) { - if !strings.HasPrefix(address, "http://") && !strings.HasPrefix(address, "https://") { - address = "https://" + address - } - - address, err := trimV1Address(address) - if err != nil { - return nil, err - } - - uri, err := url.Parse(address) - if err != nil { - return nil, invalidParam(err) - } - - // TODO(tiborvass): make sure a ConnectTimeout transport is used - tr := newTransport(tlsConfig) - - return &v1Endpoint{ - IsSecure: tlsConfig == nil || !tlsConfig.InsecureSkipVerify, - URL: uri, - client: httpClient(transport.NewTransport(tr, Headers("", headers)...)), - }, nil -} - -// Get the formatted URL for the root of this registry Endpoint -func (e *v1Endpoint) String() string { - return e.URL.String() + "/v1/" -} - -// ping returns a v1PingResult which indicates whether the registry is standalone or not. -func (e *v1Endpoint) ping(ctx context.Context) (v1PingResult, error) { - if e.String() == IndexServer { - // Skip the check, we know this one is valid - // (and we never want to fallback to http in case of error) - return v1PingResult{}, nil - } - - pingURL := e.String() + "_ping" - log.G(ctx).WithField("url", pingURL).Debug("attempting v1 ping for registry endpoint") - req, err := http.NewRequestWithContext(ctx, http.MethodGet, pingURL, http.NoBody) - if err != nil { - return v1PingResult{}, invalidParam(err) - } - - resp, err := e.client.Do(req) - if err != nil { - if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - return v1PingResult{}, err - } - return v1PingResult{}, invalidParam(err) - } - - defer resp.Body.Close() - - if v := resp.Header.Get("X-Docker-Registry-Standalone"); v != "" { - info := v1PingResult{} - // Accepted values are "1", and "true" (case-insensitive). - if v == "1" || strings.EqualFold(v, "true") { - info.Standalone = true - } - log.G(ctx).Debugf("v1PingResult.Standalone (from X-Docker-Registry-Standalone header): %t", info.Standalone) - return info, nil - } - - // If the header is absent, we assume true for compatibility with earlier - // versions of the registry. default to true - info := v1PingResult{ - Standalone: true, - } - if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { - log.G(ctx).WithError(err).Debug("error unmarshaling _ping response") - // don't stop here. Just assume sane defaults - } - - log.G(ctx).Debugf("v1PingResult.Standalone: %t", info.Standalone) - return info, nil -} - -// httpClient returns an HTTP client structure which uses the given transport -// and contains the necessary headers for redirected requests -func httpClient(tr http.RoundTripper) *http.Client { - return &http.Client{ - Transport: tr, - CheckRedirect: addRequiredHeadersToRedirectedRequests, - } -} - -func trustedLocation(req *http.Request) bool { - var ( - trusteds = []string{"docker.com", "docker.io"} - hostname = strings.SplitN(req.Host, ":", 2)[0] - ) - if req.URL.Scheme != "https" { - return false - } - - for _, trusted := range trusteds { - if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) { - return true - } - } - return false -} - -// addRequiredHeadersToRedirectedRequests adds the necessary redirection headers -// for redirected requests -func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error { - if len(via) != 0 && via[0] != nil { - if trustedLocation(req) && trustedLocation(via[0]) { - req.Header = via[0].Header - return nil - } - for k, v := range via[0].Header { - if k != "Authorization" { - for _, vv := range v { - req.Header.Add(k, vv) - } - } - } - } - return nil -} diff --git a/internal/registry/search_endpoint_v1_test.go b/internal/registry/search_endpoint_v1_test.go deleted file mode 100644 index a430f34cc1de..000000000000 --- a/internal/registry/search_endpoint_v1_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package registry - -import ( - "context" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/docker/docker/api/types/registry" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" -) - -func TestV1EndpointPing(t *testing.T) { - testPing := func(index *registry.IndexInfo, expectedStandalone bool, assertMessage string) { - ep, err := newV1Endpoint(context.Background(), index, nil) - if err != nil { - t.Fatal(err) - } - regInfo, err := ep.ping(context.Background()) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, regInfo.Standalone, expectedStandalone, assertMessage) - } - - testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)") - testPing(makeHTTPSIndex("/v1/"), true, "Expected standalone to be true (default)") - testPing(makePublicIndex(), false, "Expected standalone to be false for public index") -} - -func TestV1Endpoint(t *testing.T) { - // Simple wrapper to fail test if err != nil - expandEndpoint := func(index *registry.IndexInfo) *v1Endpoint { - endpoint, err := newV1Endpoint(context.Background(), index, nil) - if err != nil { - t.Fatal(err) - } - return endpoint - } - - assertInsecureIndex := func(index *registry.IndexInfo) { - index.Secure = true - _, err := newV1Endpoint(context.Background(), index, nil) - assert.ErrorContains(t, err, "insecure-registry", index.Name+": Expected insecure-registry error for insecure index") - index.Secure = false - } - - assertSecureIndex := func(index *registry.IndexInfo) { - index.Secure = true - _, err := newV1Endpoint(context.Background(), index, nil) - assert.ErrorContains(t, err, "certificate signed by unknown authority", index.Name+": Expected cert error for secure index") - index.Secure = false - } - - index := ®istry.IndexInfo{} - index.Name = makeURL("/v1/") - endpoint := expandEndpoint(index) - assert.Equal(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name) - assertInsecureIndex(index) - - index.Name = makeURL("") - endpoint = expandEndpoint(index) - assert.Equal(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/") - assertInsecureIndex(index) - - httpURL := makeURL("") - index.Name = strings.SplitN(httpURL, "://", 2)[1] - endpoint = expandEndpoint(index) - assert.Equal(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/") - assertInsecureIndex(index) - - index.Name = makeHTTPSURL("/v1/") - endpoint = expandEndpoint(index) - assert.Equal(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name) - assertSecureIndex(index) - - index.Name = makeHTTPSURL("") - endpoint = expandEndpoint(index) - assert.Equal(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/") - assertSecureIndex(index) - - httpsURL := makeHTTPSURL("") - index.Name = strings.SplitN(httpsURL, "://", 2)[1] - endpoint = expandEndpoint(index) - assert.Equal(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/") - assertSecureIndex(index) - - badEndpoints := []string{ - "http://127.0.0.1/v1/", - "https://127.0.0.1/v1/", - "http://127.0.0.1", - "https://127.0.0.1", - "127.0.0.1", - } - for _, address := range badEndpoints { - index.Name = address - _, err := newV1Endpoint(context.Background(), index, nil) - assert.Check(t, err != nil, "Expected error while expanding bad endpoint: %s", address) - } -} - -func TestV1EndpointParse(t *testing.T) { - tests := []struct { - address string - expected string - expectedErr string - }{ - { - address: IndexServer, - expected: IndexServer, - }, - { - address: "https://0.0.0.0:5000/v1/", - expected: "https://0.0.0.0:5000/v1/", - }, - { - address: "https://0.0.0.0:5000", - expected: "https://0.0.0.0:5000/v1/", - }, - { - address: "0.0.0.0:5000", - expected: "https://0.0.0.0:5000/v1/", - }, - { - address: "https://0.0.0.0:5000/nonversion/", - expected: "https://0.0.0.0:5000/nonversion/v1/", - }, - { - address: "https://0.0.0.0:5000/v0/", - expected: "https://0.0.0.0:5000/v0/v1/", - }, - { - address: "https://0.0.0.0:5000/v2/", - expectedErr: "search is not supported on v2 endpoints: https://0.0.0.0:5000/v2/", - }, - } - for _, tc := range tests { - t.Run(tc.address, func(t *testing.T) { - ep, err := newV1EndpointFromStr(tc.address, nil, nil) - if tc.expectedErr != "" { - assert.Check(t, is.Error(err, tc.expectedErr)) - assert.Check(t, is.Nil(ep)) - } else { - assert.NilError(t, err) - assert.Check(t, is.Equal(ep.String(), tc.expected)) - } - }) - } -} - -// Ensure that a registry endpoint that responds with a 401 only is determined -// to be a valid v1 registry endpoint -func TestV1EndpointValidate(t *testing.T) { - requireBasicAuthHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("WWW-Authenticate", `Basic realm="localhost"`) - w.WriteHeader(http.StatusUnauthorized) - }) - - // Make a test server which should validate as a v1 server. - testServer := httptest.NewServer(requireBasicAuthHandler) - defer testServer.Close() - - testEndpoint, err := newV1Endpoint(context.Background(), ®istry.IndexInfo{Name: testServer.URL}, nil) - if err != nil { - t.Fatal(err) - } - - if testEndpoint.URL.Scheme != "http" { - t.Fatalf("expecting to validate endpoint as http, got url %s", testEndpoint.String()) - } -} - -func TestTrustedLocation(t *testing.T) { - for _, u := range []string{"http://example.com", "https://example.com:7777", "http://docker.io", "http://test.docker.com", "https://fakedocker.com"} { - req, _ := http.NewRequest(http.MethodGet, u, http.NoBody) - assert.Check(t, !trustedLocation(req)) - } - - for _, u := range []string{"https://docker.io", "https://test.docker.com:80"} { - req, _ := http.NewRequest(http.MethodGet, u, http.NoBody) - assert.Check(t, trustedLocation(req)) - } -} - -func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) { - for _, urls := range [][]string{ - {"http://docker.io", "https://docker.com"}, - {"https://foo.docker.io:7777", "http://bar.docker.com"}, - {"https://foo.docker.io", "https://example.com"}, - } { - reqFrom, _ := http.NewRequest(http.MethodGet, urls[0], http.NoBody) - reqFrom.Header.Add("Content-Type", "application/json") - reqFrom.Header.Add("Authorization", "super_secret") - reqTo, _ := http.NewRequest(http.MethodGet, urls[1], http.NoBody) - - _ = addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) - - if len(reqTo.Header) != 1 { - t.Fatalf("Expected 1 headers, got %d", len(reqTo.Header)) - } - - if reqTo.Header.Get("Content-Type") != "application/json" { - t.Fatal("'Content-Type' should be 'application/json'") - } - - if reqTo.Header.Get("Authorization") != "" { - t.Fatal("'Authorization' should be empty") - } - } - - for _, urls := range [][]string{ - {"https://docker.io", "https://docker.com"}, - {"https://foo.docker.io:7777", "https://bar.docker.com"}, - } { - reqFrom, _ := http.NewRequest(http.MethodGet, urls[0], http.NoBody) - reqFrom.Header.Add("Content-Type", "application/json") - reqFrom.Header.Add("Authorization", "super_secret") - reqTo, _ := http.NewRequest(http.MethodGet, urls[1], http.NoBody) - - _ = addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) - - if len(reqTo.Header) != 2 { - t.Fatalf("Expected 2 headers, got %d", len(reqTo.Header)) - } - - if reqTo.Header.Get("Content-Type") != "application/json" { - t.Fatal("'Content-Type' should be 'application/json'") - } - - if reqTo.Header.Get("Authorization") != "super_secret" { - t.Fatal("'Authorization' should be 'super_secret'") - } - } -} diff --git a/internal/registry/search_session.go b/internal/registry/search_session.go deleted file mode 100644 index 825777ac2c2b..000000000000 --- a/internal/registry/search_session.go +++ /dev/null @@ -1,248 +0,0 @@ -package registry - -import ( - // this is required for some certificates - "context" - _ "crypto/sha512" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/cookiejar" - "net/url" - "strconv" - "strings" - "sync" - - "github.com/containerd/log" - "github.com/docker/docker/api/types/registry" - "github.com/pkg/errors" -) - -// A session is used to communicate with a V1 registry -type session struct { - indexEndpoint *v1Endpoint - client *http.Client -} - -type authTransport struct { - base http.RoundTripper - authConfig *registry.AuthConfig - - alwaysSetBasicAuth bool - token []string - - mu sync.Mutex // guards modReq - modReq map[*http.Request]*http.Request // original -> modified -} - -// newAuthTransport handles the auth layer when communicating with a v1 registry (private or official) -// -// For private v1 registries, set alwaysSetBasicAuth to true. -// -// For the official v1 registry, if there isn't already an Authorization header in the request, -// but there is an X-Docker-Token header set to true, then Basic Auth will be used to set the Authorization header. -// After sending the request with the provided base http.RoundTripper, if an X-Docker-Token header, representing -// a token, is present in the response, then it gets cached and sent in the Authorization header of all subsequent -// requests. -// -// If the server sends a token without the client having requested it, it is ignored. -// -// This RoundTripper also has a CancelRequest method important for correct timeout handling. -func newAuthTransport(base http.RoundTripper, authConfig *registry.AuthConfig, alwaysSetBasicAuth bool) *authTransport { - if base == nil { - base = http.DefaultTransport - } - return &authTransport{ - base: base, - authConfig: authConfig, - alwaysSetBasicAuth: alwaysSetBasicAuth, - modReq: make(map[*http.Request]*http.Request), - } -} - -// cloneRequest returns a clone of the provided *http.Request. -// The clone is a shallow copy of the struct and its Header map. -func cloneRequest(r *http.Request) *http.Request { - // shallow copy of the struct - r2 := new(http.Request) - *r2 = *r - // deep copy of the Header - r2.Header = make(http.Header, len(r.Header)) - for k, s := range r.Header { - r2.Header[k] = append([]string(nil), s...) - } - - return r2 -} - -// onEOFReader wraps an io.ReadCloser and a function -// the function will run at the end of file or close the file. -type onEOFReader struct { - Rc io.ReadCloser - Fn func() -} - -func (r *onEOFReader) Read(p []byte) (int, error) { - n, err := r.Rc.Read(p) - if err == io.EOF { - r.runFunc() - } - return n, err -} - -// Close closes the file and run the function. -func (r *onEOFReader) Close() error { - err := r.Rc.Close() - r.runFunc() - return err -} - -func (r *onEOFReader) runFunc() { - if fn := r.Fn; fn != nil { - fn() - r.Fn = nil - } -} - -// RoundTrip changes an HTTP request's headers to add the necessary -// authentication-related headers -func (tr *authTransport) RoundTrip(orig *http.Request) (*http.Response, error) { - // Authorization should not be set on 302 redirect for untrusted locations. - // This logic mirrors the behavior in addRequiredHeadersToRedirectedRequests. - // As the authorization logic is currently implemented in RoundTrip, - // a 302 redirect is detected by looking at the Referrer header as go http package adds said header. - // This is safe as Docker doesn't set Referrer in other scenarios. - if orig.Header.Get("Referer") != "" && !trustedLocation(orig) { - return tr.base.RoundTrip(orig) - } - - req := cloneRequest(orig) - tr.mu.Lock() - tr.modReq[orig] = req - tr.mu.Unlock() - - if tr.alwaysSetBasicAuth { - if tr.authConfig == nil { - return nil, errors.New("unexpected error: empty auth config") - } - req.SetBasicAuth(tr.authConfig.Username, tr.authConfig.Password) - return tr.base.RoundTrip(req) - } - - // Don't override - if req.Header.Get("Authorization") == "" { - if req.Header.Get("X-Docker-Token") == "true" && tr.authConfig != nil && tr.authConfig.Username != "" { - req.SetBasicAuth(tr.authConfig.Username, tr.authConfig.Password) - } else if len(tr.token) > 0 { - req.Header.Set("Authorization", "Token "+strings.Join(tr.token, ",")) - } - } - resp, err := tr.base.RoundTrip(req) - if err != nil { - tr.mu.Lock() - delete(tr.modReq, orig) - tr.mu.Unlock() - return nil, err - } - if len(resp.Header["X-Docker-Token"]) > 0 { - tr.token = resp.Header["X-Docker-Token"] - } - resp.Body = &onEOFReader{ - Rc: resp.Body, - Fn: func() { - tr.mu.Lock() - delete(tr.modReq, orig) - tr.mu.Unlock() - }, - } - return resp, nil -} - -// CancelRequest cancels an in-flight request by closing its connection. -func (tr *authTransport) CancelRequest(req *http.Request) { - type canceler interface { - CancelRequest(*http.Request) - } - if cr, ok := tr.base.(canceler); ok { - tr.mu.Lock() - modReq := tr.modReq[req] - delete(tr.modReq, req) - tr.mu.Unlock() - cr.CancelRequest(modReq) - } -} - -func authorizeClient(ctx context.Context, client *http.Client, authConfig *registry.AuthConfig, endpoint *v1Endpoint) error { - var alwaysSetBasicAuth bool - - // If we're working with a standalone private registry over HTTPS, send Basic Auth headers - // alongside all our requests. - if endpoint.String() != IndexServer && endpoint.URL.Scheme == "https" { - info, err := endpoint.ping(ctx) - if err != nil { - return err - } - if info.Standalone && authConfig != nil { - log.G(ctx).WithField("endpoint", endpoint.String()).Debug("Endpoint is eligible for private registry; enabling alwaysSetBasicAuth") - alwaysSetBasicAuth = true - } - } - - // Annotate the transport unconditionally so that v2 can - // properly fallback on v1 when an image is not found. - client.Transport = newAuthTransport(client.Transport, authConfig, alwaysSetBasicAuth) - - jar, err := cookiejar.New(nil) - if err != nil { - return systemErr{errors.New("cookiejar.New is not supposed to return an error")} - } - client.Jar = jar - - return nil -} - -func newSession(client *http.Client, endpoint *v1Endpoint) *session { - return &session{ - client: client, - indexEndpoint: endpoint, - } -} - -// defaultSearchLimit is the default value for maximum number of returned search results. -const defaultSearchLimit = 25 - -// searchRepositories performs a search against the remote repository -func (r *session) searchRepositories(ctx context.Context, term string, limit int) (*registry.SearchResults, error) { - if limit == 0 { - limit = defaultSearchLimit - } - if limit < 1 || limit > 100 { - return nil, invalidParamf("limit %d is outside the range of [1, 100]", limit) - } - u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(strconv.Itoa(limit)) - log.G(ctx).WithField("url", u).Debug("searchRepositories") - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, http.NoBody) - if err != nil { - return nil, invalidParamWrapf(err, "error building request") - } - // Have the AuthTransport send authentication, when logged in. - req.Header.Set("X-Docker-Token", "true") - res, err := r.client.Do(req) - if err != nil { - return nil, systemErr{err} - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - // TODO(thaJeztah): return upstream response body for errors (see https://github.com/moby/moby/issues/27286). - // TODO(thaJeztah): handle other status-codes to return correct error-type - return nil, errUnknown{fmt.Errorf("unexpected status code %d", res.StatusCode)} - } - result := ®istry.SearchResults{} - err = json.NewDecoder(res.Body).Decode(result) - if err != nil { - return nil, systemErr{errors.Wrap(err, "error decoding registry search results")} - } - return result, nil -} diff --git a/internal/registry/search_test.go b/internal/registry/search_test.go deleted file mode 100644 index d9f563df173e..000000000000 --- a/internal/registry/search_test.go +++ /dev/null @@ -1,418 +0,0 @@ -package registry - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "net/http/httputil" - "testing" - - cerrdefs "github.com/containerd/errdefs" - "github.com/docker/distribution/registry/client/transport" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/registry" - "gotest.tools/v3/assert" -) - -func spawnTestRegistrySession(t *testing.T) *session { - t.Helper() - authConfig := ®istry.AuthConfig{} - endpoint, err := newV1Endpoint(context.Background(), makeIndex("/v1/"), nil) - if err != nil { - t.Fatal(err) - } - userAgent := "docker test client" - var tr http.RoundTripper = debugTransport{newTransport(nil), t.Log} - tr = transport.NewTransport(newAuthTransport(tr, authConfig, false), Headers(userAgent, nil)...) - client := httpClient(tr) - - if err := authorizeClient(context.Background(), client, authConfig, endpoint); err != nil { - t.Fatal(err) - } - r := newSession(client, endpoint) - - // In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true` - // header while authenticating, in order to retrieve a token that can be later used to - // perform authenticated actions. - // - // The mock v1 registry does not support that, (TODO(tiborvass): support it), instead, - // it will consider authenticated any request with the header `X-Docker-Token: fake-token`. - // - // Because we know that the client's transport is an `*authTransport` we simply cast it, - // in order to set the internal cached token to the fake token, and thus send that fake token - // upon every subsequent requests. - r.client.Transport.(*authTransport).token = []string{"fake-token"} - return r -} - -type debugTransport struct { - http.RoundTripper - log func(...interface{}) -} - -func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) { - dump, err := httputil.DumpRequestOut(req, false) - if err != nil { - tr.log("could not dump request") - } - tr.log(string(dump)) - resp, err := tr.RoundTripper.RoundTrip(req) - if err != nil { - return nil, err - } - dump, err = httputil.DumpResponse(resp, false) - if err != nil { - tr.log("could not dump response") - } - tr.log(string(dump)) - return resp, err -} - -func TestSearchRepositories(t *testing.T) { - r := spawnTestRegistrySession(t) - results, err := r.searchRepositories(context.Background(), "fakequery", 25) - if err != nil { - t.Fatal(err) - } - if results == nil { - t.Fatal("Expected non-nil SearchResults object") - } - assert.Equal(t, results.NumResults, 1, "Expected 1 search results") - assert.Equal(t, results.Query, "fakequery", "Expected 'fakequery' as query") - assert.Equal(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars") -} - -func TestSearchErrors(t *testing.T) { - errorCases := []struct { - filtersArgs filters.Args - shouldReturnError bool - expectedError string - }{ - { - expectedError: "unexpected status code 500", - shouldReturnError: true, - }, - { - filtersArgs: filters.NewArgs(filters.Arg("type", "custom")), - expectedError: "invalid filter 'type'", - }, - { - filtersArgs: filters.NewArgs(filters.Arg("is-automated", "invalid")), - expectedError: "invalid filter 'is-automated=[invalid]'", - }, - { - filtersArgs: filters.NewArgs( - filters.Arg("is-automated", "true"), - filters.Arg("is-automated", "false"), - ), - expectedError: "invalid filter 'is-automated", - }, - { - filtersArgs: filters.NewArgs(filters.Arg("is-official", "invalid")), - expectedError: "invalid filter 'is-official=[invalid]'", - }, - { - filtersArgs: filters.NewArgs( - filters.Arg("is-official", "true"), - filters.Arg("is-official", "false"), - ), - expectedError: "invalid filter 'is-official", - }, - { - filtersArgs: filters.NewArgs(filters.Arg("stars", "invalid")), - expectedError: "invalid filter 'stars=invalid'", - }, - { - filtersArgs: filters.NewArgs( - filters.Arg("stars", "1"), - filters.Arg("stars", "invalid"), - ), - expectedError: "invalid filter 'stars=invalid'", - }, - } - for _, tc := range errorCases { - t.Run(tc.expectedError, func(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !tc.shouldReturnError { - t.Errorf("unexpected HTTP request") - } - http.Error(w, "no search for you", http.StatusInternalServerError) - })) - defer srv.Close() - - // Construct the search term by cutting the 'http://' prefix off srv.URL. - term := srv.URL[7:] + "/term" - - reg, err := NewService(ServiceOptions{}) - assert.NilError(t, err) - _, err = reg.Search(context.Background(), tc.filtersArgs, term, 0, nil, map[string][]string{}) - assert.ErrorContains(t, err, tc.expectedError) - if tc.shouldReturnError { - assert.Check(t, cerrdefs.IsUnknown(err), "got: %T: %v", err, err) - return - } - assert.Check(t, cerrdefs.IsInvalidArgument(err), "got: %T: %v", err, err) - }) - } -} - -func TestSearch(t *testing.T) { - const term = "term" - successCases := []struct { - name string - filtersArgs filters.Args - registryResults []registry.SearchResult - expectedResults []registry.SearchResult - }{ - { - name: "empty results", - registryResults: []registry.SearchResult{}, - expectedResults: []registry.SearchResult{}, - }, - { - name: "no filter", - registryResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - }, - }, - expectedResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - }, - }, - }, - { - name: "is-automated=true, no results", - filtersArgs: filters.NewArgs(filters.Arg("is-automated", "true")), - registryResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - }, - }, - expectedResults: []registry.SearchResult{}, - }, - { - name: "is-automated=true", - filtersArgs: filters.NewArgs(filters.Arg("is-automated", "true")), - registryResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated). - }, - }, - expectedResults: []registry.SearchResult{}, - }, - { - name: "is-automated=false, IsAutomated reset to false", - filtersArgs: filters.NewArgs(filters.Arg("is-automated", "false")), - registryResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated). - }, - }, - expectedResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - IsAutomated: false, //nolint:staticcheck // ignore SA1019 (field is deprecated). - }, - }, - }, - { - name: "is-automated=false", - filtersArgs: filters.NewArgs(filters.Arg("is-automated", "false")), - registryResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - }, - }, - expectedResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - }, - }, - }, - { - name: "is-official=true, no results", - filtersArgs: filters.NewArgs(filters.Arg("is-official", "true")), - registryResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - }, - }, - expectedResults: []registry.SearchResult{}, - }, - { - name: "is-official=true", - filtersArgs: filters.NewArgs(filters.Arg("is-official", "true")), - registryResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - IsOfficial: true, - }, - }, - expectedResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - IsOfficial: true, - }, - }, - }, - { - name: "is-official=false, no results", - filtersArgs: filters.NewArgs(filters.Arg("is-official", "false")), - registryResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - IsOfficial: true, - }, - }, - expectedResults: []registry.SearchResult{}, - }, - { - name: "is-official=false", - filtersArgs: filters.NewArgs(filters.Arg("is-official", "false")), - registryResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - IsOfficial: false, - }, - }, - expectedResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - IsOfficial: false, - }, - }, - }, - { - name: "stars=0", - filtersArgs: filters.NewArgs(filters.Arg("stars", "0")), - registryResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - StarCount: 0, - }, - }, - expectedResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - StarCount: 0, - }, - }, - }, - { - name: "stars=0, no results", - filtersArgs: filters.NewArgs(filters.Arg("stars", "1")), - registryResults: []registry.SearchResult{ - { - Name: "name", - Description: "description", - StarCount: 0, - }, - }, - expectedResults: []registry.SearchResult{}, - }, - { - name: "stars=1", - filtersArgs: filters.NewArgs(filters.Arg("stars", "1")), - registryResults: []registry.SearchResult{ - { - Name: "name0", - Description: "description0", - StarCount: 0, - }, - { - Name: "name1", - Description: "description1", - StarCount: 1, - }, - }, - expectedResults: []registry.SearchResult{ - { - Name: "name1", - Description: "description1", - StarCount: 1, - }, - }, - }, - { - name: "stars=1, is-official=true, is-automated=true", - filtersArgs: filters.NewArgs( - filters.Arg("stars", "1"), - filters.Arg("is-official", "true"), - filters.Arg("is-automated", "true"), - ), - registryResults: []registry.SearchResult{ - { - Name: "name0", - Description: "description0", - StarCount: 0, - IsOfficial: true, - IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated). - }, - { - Name: "name1", - Description: "description1", - StarCount: 1, - IsOfficial: false, - IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated). - }, - { - Name: "name2", - Description: "description2", - StarCount: 1, - IsOfficial: true, - }, - { - Name: "name3", - Description: "description3", - StarCount: 2, - IsOfficial: true, - IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated). - }, - }, - expectedResults: []registry.SearchResult{}, - }, - } - for _, tc := range successCases { - t.Run(tc.name, func(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-type", "application/json") - json.NewEncoder(w).Encode(registry.SearchResults{ - Query: term, - NumResults: len(tc.registryResults), - Results: tc.registryResults, - }) - })) - defer srv.Close() - - // Construct the search term by cutting the 'http://' prefix off srv.URL. - searchTerm := srv.URL[7:] + "/" + term - - reg, err := NewService(ServiceOptions{}) - assert.NilError(t, err) - results, err := reg.Search(context.Background(), tc.filtersArgs, searchTerm, 0, nil, map[string][]string{}) - assert.NilError(t, err) - assert.DeepEqual(t, results, tc.expectedResults) - }) - } -} diff --git a/internal/registry/service.go b/internal/registry/service.go index 73c9edfcd102..0b6052cb5e0d 100644 --- a/internal/registry/service.go +++ b/internal/registry/service.go @@ -6,11 +6,9 @@ import ( "errors" "net/url" "strings" - "sync" cerrdefs "github.com/containerd/errdefs" "github.com/containerd/log" - "github.com/distribution/reference" "github.com/docker/docker/api/types/registry" ) @@ -18,7 +16,6 @@ import ( // of mirrors. type Service struct { config *serviceConfig - mu sync.RWMutex } // NewService returns a new instance of [Service] ready to be installed into @@ -32,27 +29,6 @@ func NewService(options ServiceOptions) (*Service, error) { return &Service{config: config}, err } -// ServiceConfig returns a copy of the public registry service's configuration. -func (s *Service) ServiceConfig() *registry.ServiceConfig { - s.mu.RLock() - defer s.mu.RUnlock() - return s.config.copy() -} - -// ReplaceConfig prepares a transaction which will atomically replace the -// registry service's configuration when the returned commit function is called. -func (s *Service) ReplaceConfig(options ServiceOptions) (commit func(), _ error) { - config, err := newServiceConfig(options) - if err != nil { - return nil, err - } - return func() { - s.mu.Lock() - defer s.mu.Unlock() - s.config = config - }, nil -} - // Auth contacts the public registry with the provided credentials, // and returns OK if authentication was successful. // It can be used to verify the validity of a client's credentials. @@ -74,9 +50,7 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use // Lookup endpoints for authentication but exclude mirrors to prevent // sending credentials of the upstream registry to a mirror. - s.mu.RLock() endpoints, err := s.lookupV2Endpoints(ctx, registryHostName, false) - s.mu.RUnlock() if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return "", "", err @@ -108,24 +82,6 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use return "", "", lastErr } -// ResolveAuthConfig looks up authentication for the given reference from the -// given authConfigs. -// -// IMPORTANT: This function is for internal use and should not be used by external projects. -func (s *Service) ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, ref reference.Named) registry.AuthConfig { - s.mu.RLock() - defer s.mu.RUnlock() - // Simplified version of "newIndexInfo" without handling of insecure - // registries and mirrors, as we don't need that information to resolve - // the auth-config. - indexName := normalizeIndexName(reference.Domain(ref)) - registryInfo, ok := s.config.IndexConfigs[indexName] - if !ok { - registryInfo = ®istry.IndexInfo{Name: indexName} - } - return ResolveAuthConfig(authConfigs, registryInfo) -} - // APIEndpoint represents a remote API endpoint type APIEndpoint struct { Mirror bool @@ -136,25 +92,11 @@ type APIEndpoint struct { // LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference. // It gives preference to mirrors over the actual registry, and HTTPS over plain HTTP. func (s *Service) LookupPullEndpoints(hostname string) ([]APIEndpoint, error) { - s.mu.RLock() - defer s.mu.RUnlock() - return s.lookupV2Endpoints(context.TODO(), hostname, true) } // LookupPushEndpoints creates a list of v2 endpoints to try to push to, in order of preference. // It gives preference to HTTPS over plain HTTP. Mirrors are not included. func (s *Service) LookupPushEndpoints(hostname string) ([]APIEndpoint, error) { - s.mu.RLock() - defer s.mu.RUnlock() - return s.lookupV2Endpoints(context.TODO(), hostname, false) } - -// IsInsecureRegistry returns true if the registry at given host is configured as -// insecure registry. -func (s *Service) IsInsecureRegistry(host string) bool { - s.mu.RLock() - defer s.mu.RUnlock() - return !s.config.isSecureIndex(host) -} From 72f85ccd16c006f77b3799d2db702d42108f1ee5 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 24 Jul 2025 01:57:57 +0200 Subject: [PATCH 015/193] internal/registry: remove code related to mirrors The CLI does not have information about mirrors, and doesn't configure them, so we can remove these parts. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit e0b351b3d9160b03cbc4ed388def82673b3d71f0) Signed-off-by: Sebastiaan van Stijn --- internal/registry/config.go | 83 +------ internal/registry/config_test.go | 182 -------------- internal/registry/registry_test.go | 373 ----------------------------- internal/registry/service.go | 21 +- internal/registry/service_v2.go | 27 +-- 5 files changed, 13 insertions(+), 673 deletions(-) diff --git a/internal/registry/config.go b/internal/registry/config.go index dac5e7fd6c11..78a2e8f52070 100644 --- a/internal/registry/config.go +++ b/internal/registry/config.go @@ -22,7 +22,6 @@ import ( // ServiceOptions holds command line options. type ServiceOptions struct { - Mirrors []string `json:"registry-mirrors,omitempty"` InsecureRegistries []string `json:"insecure-registries,omitempty"` } @@ -93,51 +92,6 @@ func CertsDir() string { return certsDir } -// newServiceConfig returns a new instance of ServiceConfig -func newServiceConfig(options ServiceOptions) (*serviceConfig, error) { - config := &serviceConfig{} - if err := config.loadMirrors(options.Mirrors); err != nil { - return nil, err - } - if err := config.loadInsecureRegistries(options.InsecureRegistries); err != nil { - return nil, err - } - - return config, nil -} - -// loadMirrors loads mirrors to config, after removing duplicates. -// Returns an error if mirrors contains an invalid mirror. -func (config *serviceConfig) loadMirrors(mirrors []string) error { - mMap := map[string]struct{}{} - unique := []string{} - - for _, mirror := range mirrors { - m, err := ValidateMirror(mirror) - if err != nil { - return err - } - if _, exist := mMap[m]; !exist { - mMap[m] = struct{}{} - unique = append(unique, m) - } - } - - config.Mirrors = unique - - // Configure public registry since mirrors may have changed. - config.IndexConfigs = map[string]*registry.IndexInfo{ - IndexName: { - Name: IndexName, - Mirrors: unique, - Secure: true, - Official: true, - }, - } - - return nil -} - // loadInsecureRegistries loads insecure registries to config func (config *serviceConfig) loadInsecureRegistries(registries []string) error { // Localhost is by default considered as an insecure registry. This is a @@ -184,7 +138,6 @@ skip: // Assume `host:port` if not CIDR. indexConfigs[r] = ®istry.IndexInfo{ Name: r, - Mirrors: []string{}, Secure: false, Official: false, } @@ -194,7 +147,6 @@ skip: // Configure public registry. indexConfigs[IndexName] = ®istry.IndexInfo{ Name: IndexName, - Mirrors: config.Mirrors, Secure: true, Official: true, } @@ -267,35 +219,6 @@ func isCIDRMatch(cidrs []*registry.NetIPNet, urlHost string) bool { return false } -// ValidateMirror validates and normalizes an HTTP(S) registry mirror. It -// returns an error if the given mirrorURL is invalid, or the normalized -// format for the URL otherwise. -// -// It is used by the daemon to validate the daemon configuration. -func ValidateMirror(mirrorURL string) (string, error) { - // Fast path for missing scheme, as url.Parse splits by ":", which can - // cause the hostname to be considered the "scheme" when using "hostname:port". - if scheme, _, ok := strings.Cut(mirrorURL, "://"); !ok || scheme == "" { - return "", invalidParamf("invalid mirror: no scheme specified for %q: must use either 'https://' or 'http://'", mirrorURL) - } - uri, err := url.Parse(mirrorURL) - if err != nil { - return "", invalidParamWrapf(err, "invalid mirror: %q is not a valid URI", mirrorURL) - } - if uri.Scheme != "http" && uri.Scheme != "https" { - return "", invalidParamf("invalid mirror: unsupported scheme %q in %q: must use either 'https://' or 'http://'", uri.Scheme, uri) - } - if uri.RawQuery != "" || uri.Fragment != "" { - return "", invalidParamf("invalid mirror: query or fragment at end of the URI %q", uri) - } - if uri.User != nil { - // strip password from output - uri.User = url.UserPassword(uri.User.Username(), "xxxxx") - return "", invalidParamf("invalid mirror: username/password not allowed in URI %q", uri) - } - return strings.TrimSuffix(mirrorURL, "/") + "/", nil -} - // ValidateIndexName validates an index name. It is used by the daemon to // validate the daemon configuration. func ValidateIndexName(val string) (string, error) { @@ -348,7 +271,6 @@ func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) { Name: reference.TrimNamed(reposName), Index: ®istry.IndexInfo{ Name: IndexName, - Mirrors: []string{}, Secure: true, Official: true, }, @@ -358,9 +280,8 @@ func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) { return &RepositoryInfo{ Name: reference.TrimNamed(reposName), Index: ®istry.IndexInfo{ - Name: indexName, - Mirrors: []string{}, - Secure: !isInsecure(indexName), + Name: indexName, + Secure: !isInsecure(indexName), }, }, nil } diff --git a/internal/registry/config_test.go b/internal/registry/config_test.go index 699e763be6cf..f3d8b4c44a2e 100644 --- a/internal/registry/config_test.go +++ b/internal/registry/config_test.go @@ -8,138 +8,6 @@ import ( is "gotest.tools/v3/assert/cmp" ) -func TestValidateMirror(t *testing.T) { - tests := []struct { - input string - output string - expectedErr string - }{ - // Valid cases - { - input: "http://mirror-1.example.com", - output: "http://mirror-1.example.com/", - }, - { - input: "http://mirror-1.example.com/", - output: "http://mirror-1.example.com/", - }, - { - input: "https://mirror-1.example.com", - output: "https://mirror-1.example.com/", - }, - { - input: "https://mirror-1.example.com/", - output: "https://mirror-1.example.com/", - }, - { - input: "http://localhost", - output: "http://localhost/", - }, - { - input: "https://localhost", - output: "https://localhost/", - }, - { - input: "http://localhost:5000", - output: "http://localhost:5000/", - }, - { - input: "https://localhost:5000", - output: "https://localhost:5000/", - }, - { - input: "http://127.0.0.1", - output: "http://127.0.0.1/", - }, - { - input: "https://127.0.0.1", - output: "https://127.0.0.1/", - }, - { - input: "http://127.0.0.1:5000", - output: "http://127.0.0.1:5000/", - }, - { - input: "https://127.0.0.1:5000", - output: "https://127.0.0.1:5000/", - }, - { - input: "http://mirror-1.example.com/v1/", - output: "http://mirror-1.example.com/v1/", - }, - { - input: "https://mirror-1.example.com/v1/", - output: "https://mirror-1.example.com/v1/", - }, - - // Invalid cases - { - input: "!invalid!://%as%", - expectedErr: `invalid mirror: "!invalid!://%as%" is not a valid URI: parse "!invalid!://%as%": first path segment in URL cannot contain colon`, - }, - { - input: "mirror-1.example.com", - expectedErr: `invalid mirror: no scheme specified for "mirror-1.example.com": must use either 'https://' or 'http://'`, - }, - { - input: "mirror-1.example.com:5000", - expectedErr: `invalid mirror: no scheme specified for "mirror-1.example.com:5000": must use either 'https://' or 'http://'`, - }, - { - input: "ftp://mirror-1.example.com", - expectedErr: `invalid mirror: unsupported scheme "ftp" in "ftp://mirror-1.example.com": must use either 'https://' or 'http://'`, - }, - { - input: "http://mirror-1.example.com/?q=foo", - expectedErr: `invalid mirror: query or fragment at end of the URI "http://mirror-1.example.com/?q=foo"`, - }, - { - input: "http://mirror-1.example.com/v1/?q=foo", - expectedErr: `invalid mirror: query or fragment at end of the URI "http://mirror-1.example.com/v1/?q=foo"`, - }, - { - input: "http://mirror-1.example.com/v1/?q=foo#frag", - expectedErr: `invalid mirror: query or fragment at end of the URI "http://mirror-1.example.com/v1/?q=foo#frag"`, - }, - { - input: "http://mirror-1.example.com?q=foo", - expectedErr: `invalid mirror: query or fragment at end of the URI "http://mirror-1.example.com?q=foo"`, - }, - { - input: "https://mirror-1.example.com#frag", - expectedErr: `invalid mirror: query or fragment at end of the URI "https://mirror-1.example.com#frag"`, - }, - { - input: "https://mirror-1.example.com/#frag", - expectedErr: `invalid mirror: query or fragment at end of the URI "https://mirror-1.example.com/#frag"`, - }, - { - input: "http://foo:bar@mirror-1.example.com/", - expectedErr: `invalid mirror: username/password not allowed in URI "http://foo:xxxxx@mirror-1.example.com/"`, - }, - { - input: "https://mirror-1.example.com/v1/#frag", - expectedErr: `invalid mirror: query or fragment at end of the URI "https://mirror-1.example.com/v1/#frag"`, - }, - { - input: "https://mirror-1.example.com?q", - expectedErr: `invalid mirror: query or fragment at end of the URI "https://mirror-1.example.com?q"`, - }, - } - - for _, tc := range tests { - t.Run(tc.input, func(t *testing.T) { - out, err := ValidateMirror(tc.input) - if tc.expectedErr != "" { - assert.Error(t, err, tc.expectedErr) - } else { - assert.NilError(t, err) - } - assert.Check(t, is.Equal(out, tc.output)) - }) - } -} - func TestLoadInsecureRegistries(t *testing.T) { testCases := []struct { registries []string @@ -229,56 +97,6 @@ func TestLoadInsecureRegistries(t *testing.T) { } } -func TestNewServiceConfig(t *testing.T) { - tests := []struct { - doc string - opts ServiceOptions - errStr string - }{ - { - doc: "empty config", - }, - { - doc: "invalid mirror", - opts: ServiceOptions{ - Mirrors: []string{"example.com:5000"}, - }, - errStr: `invalid mirror: no scheme specified for "example.com:5000": must use either 'https://' or 'http://'`, - }, - { - doc: "valid mirror", - opts: ServiceOptions{ - Mirrors: []string{"https://example.com:5000"}, - }, - }, - { - doc: "invalid insecure registry", - opts: ServiceOptions{ - InsecureRegistries: []string{"[fe80::]/64"}, - }, - errStr: `insecure registry [fe80::]/64 is not valid: invalid host "[fe80::]/64"`, - }, - { - doc: "valid insecure registry", - opts: ServiceOptions{ - InsecureRegistries: []string{"102.10.8.1/24"}, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.doc, func(t *testing.T) { - _, err := newServiceConfig(tc.opts) - if tc.errStr != "" { - assert.Check(t, is.Error(err, tc.errStr)) - assert.Check(t, cerrdefs.IsInvalidArgument(err)) - } else { - assert.Check(t, err) - } - }) - } -} - func TestValidateIndexName(t *testing.T) { valid := []struct { index string diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go index 94d05eb4722c..f0b061b23fe9 100644 --- a/internal/registry/registry_test.go +++ b/internal/registry/registry_test.go @@ -1,8 +1,6 @@ package registry import ( - "errors" - "net" "testing" "github.com/distribution/reference" @@ -11,46 +9,6 @@ import ( is "gotest.tools/v3/assert/cmp" ) -// overrideLookupIP overrides net.LookupIP for testing. -func overrideLookupIP(t *testing.T) { - t.Helper() - restoreLookup := lookupIP - - // override net.LookupIP - lookupIP = func(host string) ([]net.IP, error) { - mockHosts := map[string][]net.IP{ - "": {net.ParseIP("0.0.0.0")}, - "localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, - "example.com": {net.ParseIP("42.42.42.42")}, - "other.com": {net.ParseIP("43.43.43.43")}, - } - if addrs, ok := mockHosts[host]; ok { - return addrs, nil - } - return nil, errors.New("lookup: no such host") - } - t.Cleanup(func() { - lookupIP = restoreLookup - }) -} - -// newIndexInfo returns IndexInfo configuration from indexName -func newIndexInfo(config *serviceConfig, indexName string) *registry.IndexInfo { - indexName = normalizeIndexName(indexName) - - // Return any configured index info, first. - if index, ok := config.IndexConfigs[indexName]; ok { - return index - } - - // Construct a non-configured index info. - return ®istry.IndexInfo{ - Name: indexName, - Mirrors: []string{}, - Secure: config.isSecureIndex(indexName), - } -} - func TestParseRepositoryInfo(t *testing.T) { type staticRepositoryInfo struct { Index *registry.IndexInfo @@ -63,7 +21,6 @@ func TestParseRepositoryInfo(t *testing.T) { "fooo/bar": { Index: ®istry.IndexInfo{ Name: IndexName, - Mirrors: []string{}, Official: true, Secure: true, }, @@ -74,7 +31,6 @@ func TestParseRepositoryInfo(t *testing.T) { "library/ubuntu": { Index: ®istry.IndexInfo{ Name: IndexName, - Mirrors: []string{}, Official: true, Secure: true, }, @@ -85,7 +41,6 @@ func TestParseRepositoryInfo(t *testing.T) { "nonlibrary/ubuntu": { Index: ®istry.IndexInfo{ Name: IndexName, - Mirrors: []string{}, Official: true, Secure: true, }, @@ -96,7 +51,6 @@ func TestParseRepositoryInfo(t *testing.T) { "ubuntu": { Index: ®istry.IndexInfo{ Name: IndexName, - Mirrors: []string{}, Official: true, Secure: true, }, @@ -107,7 +61,6 @@ func TestParseRepositoryInfo(t *testing.T) { "other/library": { Index: ®istry.IndexInfo{ Name: IndexName, - Mirrors: []string{}, Official: true, Secure: true, }, @@ -118,7 +71,6 @@ func TestParseRepositoryInfo(t *testing.T) { "127.0.0.1:8000/private/moonbase": { Index: ®istry.IndexInfo{ Name: "127.0.0.1:8000", - Mirrors: []string{}, Official: false, Secure: false, }, @@ -129,7 +81,6 @@ func TestParseRepositoryInfo(t *testing.T) { "127.0.0.1:8000/privatebase": { Index: ®istry.IndexInfo{ Name: "127.0.0.1:8000", - Mirrors: []string{}, Official: false, Secure: false, }, @@ -140,7 +91,6 @@ func TestParseRepositoryInfo(t *testing.T) { "[::1]:8000/private/moonbase": { Index: ®istry.IndexInfo{ Name: "[::1]:8000", - Mirrors: []string{}, Official: false, Secure: false, }, @@ -151,7 +101,6 @@ func TestParseRepositoryInfo(t *testing.T) { "[::1]:8000/privatebase": { Index: ®istry.IndexInfo{ Name: "[::1]:8000", - Mirrors: []string{}, Official: false, Secure: false, }, @@ -164,7 +113,6 @@ func TestParseRepositoryInfo(t *testing.T) { "[::2]:8000/private/moonbase": { Index: ®istry.IndexInfo{ Name: "[::2]:8000", - Mirrors: []string{}, Official: false, Secure: true, }, @@ -177,7 +125,6 @@ func TestParseRepositoryInfo(t *testing.T) { "[::2]:8000/privatebase": { Index: ®istry.IndexInfo{ Name: "[::2]:8000", - Mirrors: []string{}, Official: false, Secure: true, }, @@ -188,7 +135,6 @@ func TestParseRepositoryInfo(t *testing.T) { "localhost:8000/private/moonbase": { Index: ®istry.IndexInfo{ Name: "localhost:8000", - Mirrors: []string{}, Official: false, Secure: false, }, @@ -199,7 +145,6 @@ func TestParseRepositoryInfo(t *testing.T) { "localhost:8000/privatebase": { Index: ®istry.IndexInfo{ Name: "localhost:8000", - Mirrors: []string{}, Official: false, Secure: false, }, @@ -210,7 +155,6 @@ func TestParseRepositoryInfo(t *testing.T) { "example.com/private/moonbase": { Index: ®istry.IndexInfo{ Name: "example.com", - Mirrors: []string{}, Official: false, Secure: true, }, @@ -221,7 +165,6 @@ func TestParseRepositoryInfo(t *testing.T) { "example.com/privatebase": { Index: ®istry.IndexInfo{ Name: "example.com", - Mirrors: []string{}, Official: false, Secure: true, }, @@ -232,7 +175,6 @@ func TestParseRepositoryInfo(t *testing.T) { "example.com:8000/private/moonbase": { Index: ®istry.IndexInfo{ Name: "example.com:8000", - Mirrors: []string{}, Official: false, Secure: true, }, @@ -243,7 +185,6 @@ func TestParseRepositoryInfo(t *testing.T) { "example.com:8000/privatebase": { Index: ®istry.IndexInfo{ Name: "example.com:8000", - Mirrors: []string{}, Official: false, Secure: true, }, @@ -254,7 +195,6 @@ func TestParseRepositoryInfo(t *testing.T) { "localhost/private/moonbase": { Index: ®istry.IndexInfo{ Name: "localhost", - Mirrors: []string{}, Official: false, Secure: false, }, @@ -265,7 +205,6 @@ func TestParseRepositoryInfo(t *testing.T) { "localhost/privatebase": { Index: ®istry.IndexInfo{ Name: "localhost", - Mirrors: []string{}, Official: false, Secure: false, }, @@ -276,7 +215,6 @@ func TestParseRepositoryInfo(t *testing.T) { IndexName + "/public/moonbase": { Index: ®istry.IndexInfo{ Name: IndexName, - Mirrors: []string{}, Official: true, Secure: true, }, @@ -287,7 +225,6 @@ func TestParseRepositoryInfo(t *testing.T) { "index." + IndexName + "/public/moonbase": { Index: ®istry.IndexInfo{ Name: IndexName, - Mirrors: []string{}, Official: true, Secure: true, }, @@ -298,7 +235,6 @@ func TestParseRepositoryInfo(t *testing.T) { "ubuntu-12.04-base": { Index: ®istry.IndexInfo{ Name: IndexName, - Mirrors: []string{}, Official: true, Secure: true, }, @@ -309,7 +245,6 @@ func TestParseRepositoryInfo(t *testing.T) { IndexName + "/ubuntu-12.04-base": { Index: ®istry.IndexInfo{ Name: IndexName, - Mirrors: []string{}, Official: true, Secure: true, }, @@ -320,7 +255,6 @@ func TestParseRepositoryInfo(t *testing.T) { "index." + IndexName + "/ubuntu-12.04-base": { Index: ®istry.IndexInfo{ Name: IndexName, - Mirrors: []string{}, Official: true, Secure: true, }, @@ -345,310 +279,3 @@ func TestParseRepositoryInfo(t *testing.T) { }) } } - -func TestNewIndexInfo(t *testing.T) { - overrideLookupIP(t) - - // ipv6Loopback is the CIDR for the IPv6 loopback address ("::1"); "::1/128" - ipv6Loopback := &net.IPNet{ - IP: net.IPv6loopback, - Mask: net.CIDRMask(128, 128), - } - - // ipv4Loopback is the CIDR for IPv4 loopback addresses ("127.0.0.0/8") - ipv4Loopback := &net.IPNet{ - IP: net.IPv4(127, 0, 0, 0), - Mask: net.CIDRMask(8, 32), - } - - // emptyServiceConfig is a default service-config for situations where - // no config-file is available (e.g. when used in the CLI). It won't - // have mirrors configured, but does have the default insecure registry - // CIDRs for loopback interfaces configured. - emptyServiceConfig := &serviceConfig{ - IndexConfigs: map[string]*registry.IndexInfo{ - IndexName: { - Name: IndexName, - Mirrors: []string{}, - Secure: true, - Official: true, - }, - }, - InsecureRegistryCIDRs: []*registry.NetIPNet{ - (*registry.NetIPNet)(ipv6Loopback), - (*registry.NetIPNet)(ipv4Loopback), - }, - } - - expectedIndexInfos := map[string]*registry.IndexInfo{ - IndexName: { - Name: IndexName, - Official: true, - Secure: true, - Mirrors: []string{}, - }, - "index." + IndexName: { - Name: IndexName, - Official: true, - Secure: true, - Mirrors: []string{}, - }, - "example.com": { - Name: "example.com", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - "127.0.0.1:5000": { - Name: "127.0.0.1:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - } - t.Run("no mirrors", func(t *testing.T) { - for indexName, expected := range expectedIndexInfos { - t.Run(indexName, func(t *testing.T) { - actual := newIndexInfo(emptyServiceConfig, indexName) - assert.Check(t, is.DeepEqual(actual, expected)) - }) - } - }) - - expectedIndexInfos = map[string]*registry.IndexInfo{ - IndexName: { - Name: IndexName, - Official: true, - Secure: true, - Mirrors: []string{"http://mirror1.local/", "http://mirror2.local/"}, - }, - "index." + IndexName: { - Name: IndexName, - Official: true, - Secure: true, - Mirrors: []string{"http://mirror1.local/", "http://mirror2.local/"}, - }, - "example.com": { - Name: "example.com", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "example.com:5000": { - Name: "example.com:5000", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - "127.0.0.1": { - Name: "127.0.0.1", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "127.0.0.1:5000": { - Name: "127.0.0.1:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "127.255.255.255": { - Name: "127.255.255.255", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "127.255.255.255:5000": { - Name: "127.255.255.255:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "::1": { - Name: "::1", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "[::1]:5000": { - Name: "[::1]:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - // IPv6 only has a single loopback address, so ::2 is not a loopback, - // hence not marked "insecure". - "::2": { - Name: "::2", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - // IPv6 only has a single loopback address, so ::2 is not a loopback, - // hence not marked "insecure". - "[::2]:5000": { - Name: "[::2]:5000", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - "other.com": { - Name: "other.com", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - } - t.Run("mirrors", func(t *testing.T) { - // Note that newServiceConfig calls ValidateMirror internally, which normalizes - // mirror-URLs to have a trailing slash. - config, err := newServiceConfig(ServiceOptions{ - Mirrors: []string{"http://mirror1.local", "http://mirror2.local"}, - InsecureRegistries: []string{"example.com"}, - }) - assert.NilError(t, err) - for indexName, expected := range expectedIndexInfos { - t.Run(indexName, func(t *testing.T) { - actual := newIndexInfo(config, indexName) - assert.Check(t, is.DeepEqual(actual, expected)) - }) - } - }) - - expectedIndexInfos = map[string]*registry.IndexInfo{ - "example.com": { - Name: "example.com", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "example.com:5000": { - Name: "example.com:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "127.0.0.1": { - Name: "127.0.0.1", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "127.0.0.1:5000": { - Name: "127.0.0.1:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "42.42.0.1:5000": { - Name: "42.42.0.1:5000", - Official: false, - Secure: false, - Mirrors: []string{}, - }, - "42.43.0.1:5000": { - Name: "42.43.0.1:5000", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - "other.com": { - Name: "other.com", - Official: false, - Secure: true, - Mirrors: []string{}, - }, - } - t.Run("custom insecure", func(t *testing.T) { - config, err := newServiceConfig(ServiceOptions{ - InsecureRegistries: []string{"42.42.0.0/16"}, - }) - assert.NilError(t, err) - for indexName, expected := range expectedIndexInfos { - t.Run(indexName, func(t *testing.T) { - actual := newIndexInfo(config, indexName) - assert.Check(t, is.DeepEqual(actual, expected)) - }) - } - }) -} - -func TestMirrorEndpointLookup(t *testing.T) { - containsMirror := func(endpoints []APIEndpoint) bool { - for _, pe := range endpoints { - if pe.URL.Host == "my.mirror" { - return true - } - } - return false - } - cfg, err := newServiceConfig(ServiceOptions{ - Mirrors: []string{"https://my.mirror"}, - }) - assert.NilError(t, err) - s := Service{config: cfg} - - imageName, err := reference.WithName(IndexName + "/test/image") - if err != nil { - t.Error(err) - } - pushAPIEndpoints, err := s.LookupPushEndpoints(reference.Domain(imageName)) - if err != nil { - t.Fatal(err) - } - if containsMirror(pushAPIEndpoints) { - t.Fatal("Push endpoint should not contain mirror") - } - - pullAPIEndpoints, err := s.LookupPullEndpoints(reference.Domain(imageName)) - if err != nil { - t.Fatal(err) - } - if !containsMirror(pullAPIEndpoints) { - t.Fatal("Pull endpoint should contain mirror") - } -} - -func TestIsSecureIndex(t *testing.T) { - overrideLookupIP(t) - tests := []struct { - addr string - insecureRegistries []string - expected bool - }{ - {IndexName, nil, true}, - {"example.com", []string{}, true}, - {"example.com", []string{"example.com"}, false}, - {"localhost", []string{"localhost:5000"}, false}, - {"localhost:5000", []string{"localhost:5000"}, false}, - {"localhost", []string{"example.com"}, false}, - {"127.0.0.1:5000", []string{"127.0.0.1:5000"}, false}, - {"localhost", nil, false}, - {"localhost:5000", nil, false}, - {"127.0.0.1", nil, false}, - {"localhost", []string{"example.com"}, false}, - {"127.0.0.1", []string{"example.com"}, false}, - {"example.com", nil, true}, - {"example.com", []string{"example.com"}, false}, - {"127.0.0.1", []string{"example.com"}, false}, - {"127.0.0.1:5000", []string{"example.com"}, false}, - {"example.com:5000", []string{"42.42.0.0/16"}, false}, - {"example.com", []string{"42.42.0.0/16"}, false}, - {"example.com:5000", []string{"42.42.42.42/8"}, false}, - {"127.0.0.1:5000", []string{"127.0.0.0/8"}, false}, - {"42.42.42.42:5000", []string{"42.1.1.1/8"}, false}, - {"invalid.example.com", []string{"42.42.0.0/16"}, true}, - {"invalid.example.com", []string{"invalid.example.com"}, false}, - {"invalid.example.com:5000", []string{"invalid.example.com"}, true}, - {"invalid.example.com:5000", []string{"invalid.example.com:5000"}, false}, - } - for _, tc := range tests { - config, err := newServiceConfig(ServiceOptions{ - InsecureRegistries: tc.insecureRegistries, - }) - assert.NilError(t, err) - - sec := config.isSecureIndex(tc.addr) - assert.Equal(t, sec, tc.expected, "isSecureIndex failed for %q %v, expected %v got %v", tc.addr, tc.insecureRegistries, tc.expected, sec) - } -} diff --git a/internal/registry/service.go b/internal/registry/service.go index 0b6052cb5e0d..9e1accc04ccb 100644 --- a/internal/registry/service.go +++ b/internal/registry/service.go @@ -21,12 +21,13 @@ type Service struct { // NewService returns a new instance of [Service] ready to be installed into // an engine. func NewService(options ServiceOptions) (*Service, error) { - config, err := newServiceConfig(options) - if err != nil { - return nil, err + config := &serviceConfig{} + if len(options.InsecureRegistries) > 0 { + if err := config.loadInsecureRegistries(options.InsecureRegistries); err != nil { + return nil, err + } } - - return &Service{config: config}, err + return &Service{config: config}, nil } // Auth contacts the public registry with the provided credentials, @@ -48,9 +49,8 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use registryHostName = u.Host } - // Lookup endpoints for authentication but exclude mirrors to prevent - // sending credentials of the upstream registry to a mirror. - endpoints, err := s.lookupV2Endpoints(ctx, registryHostName, false) + // Lookup endpoints for authentication. + endpoints, err := s.lookupV2Endpoints(ctx, registryHostName) if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return "", "", err @@ -84,7 +84,6 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use // APIEndpoint represents a remote API endpoint type APIEndpoint struct { - Mirror bool URL *url.URL TLSConfig *tls.Config } @@ -92,11 +91,11 @@ type APIEndpoint struct { // LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference. // It gives preference to mirrors over the actual registry, and HTTPS over plain HTTP. func (s *Service) LookupPullEndpoints(hostname string) ([]APIEndpoint, error) { - return s.lookupV2Endpoints(context.TODO(), hostname, true) + return s.lookupV2Endpoints(context.TODO(), hostname) } // LookupPushEndpoints creates a list of v2 endpoints to try to push to, in order of preference. // It gives preference to HTTPS over plain HTTP. Mirrors are not included. func (s *Service) LookupPushEndpoints(hostname string) ([]APIEndpoint, error) { - return s.lookupV2Endpoints(context.TODO(), hostname, false) + return s.lookupV2Endpoints(context.TODO(), hostname) } diff --git a/internal/registry/service_v2.go b/internal/registry/service_v2.go index df343a155dec..0f0059533022 100644 --- a/internal/registry/service_v2.go +++ b/internal/registry/service_v2.go @@ -3,38 +3,13 @@ package registry import ( "context" "net/url" - "strings" "github.com/docker/go-connections/tlsconfig" ) -func (s *Service) lookupV2Endpoints(ctx context.Context, hostname string, includeMirrors bool) ([]APIEndpoint, error) { +func (s *Service) lookupV2Endpoints(ctx context.Context, hostname string) ([]APIEndpoint, error) { var endpoints []APIEndpoint if hostname == DefaultNamespace || hostname == IndexHostname { - if includeMirrors { - for _, mirror := range s.config.Mirrors { - if ctx.Err() != nil { - return nil, ctx.Err() - } - if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") { - mirror = "https://" + mirror - } - mirrorURL, err := url.Parse(mirror) - if err != nil { - return nil, invalidParam(err) - } - // TODO(thaJeztah); this should all be memoized when loading the config. We're resolving mirrors and loading TLS config every time. - mirrorTLSConfig, err := newTLSConfig(ctx, mirrorURL.Host, s.config.isSecureIndex(mirrorURL.Host)) - if err != nil { - return nil, err - } - endpoints = append(endpoints, APIEndpoint{ - URL: mirrorURL, - Mirror: true, - TLSConfig: mirrorTLSConfig, - }) - } - } endpoints = append(endpoints, APIEndpoint{ URL: DefaultV2Registry, TLSConfig: tlsconfig.ServerDefault(), From 7e01a3a8a9fb0515296d118cb85ad062547625a8 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 24 Jul 2025 02:04:37 +0200 Subject: [PATCH 016/193] internal/registry: Service.Auth remove unused statusmessage return Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 7cf245d2f7ab5338bc325913f961f6f4aec88be6) Signed-off-by: Sebastiaan van Stijn --- cli/command/registry/login.go | 2 +- internal/registry/service.go | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/cli/command/registry/login.go b/cli/command/registry/login.go index 54c3e462d643..a216908505c3 100644 --- a/cli/command/registry/login.go +++ b/cli/command/registry/login.go @@ -288,7 +288,7 @@ func loginClientSide(ctx context.Context, auth registrytypes.AuthConfig) (*regis return nil, err } - _, token, err := svc.Auth(ctx, &auth, command.UserAgent()) + token, err := svc.Auth(ctx, &auth, command.UserAgent()) if err != nil { return nil, err } diff --git a/internal/registry/service.go b/internal/registry/service.go index 9e1accc04ccb..36e95d8c63d8 100644 --- a/internal/registry/service.go +++ b/internal/registry/service.go @@ -33,8 +33,7 @@ func NewService(options ServiceOptions) (*Service, error) { // Auth contacts the public registry with the provided credentials, // and returns OK if authentication was successful. // It can be used to verify the validity of a client's credentials. -func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (statusMessage, token string, _ error) { - // TODO Use ctx when searching for repositories +func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (token string, _ error) { registryHostName := IndexHostname if authConfig.ServerAddress != "" { @@ -44,7 +43,7 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use } u, err := url.Parse(serverAddress) if err != nil { - return "", "", invalidParamWrapf(err, "unable to parse server address") + return "", invalidParamWrapf(err, "unable to parse server address") } registryHostName = u.Host } @@ -53,9 +52,9 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use endpoints, err := s.lookupV2Endpoints(ctx, registryHostName) if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - return "", "", err + return "", err } - return "", "", invalidParam(err) + return "", invalidParam(err) } var lastErr error @@ -64,7 +63,7 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || cerrdefs.IsUnauthorized(err) { // Failed to authenticate; don't continue with (non-TLS) endpoints. - return "", "", err + return "", err } // Try next endpoint log.G(ctx).WithFields(log.Fields{ @@ -75,11 +74,10 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use continue } - // TODO(thaJeztah): move the statusMessage to the API endpoint; we don't need to produce that here? - return "Login Succeeded", authToken, nil + return authToken, nil } - return "", "", lastErr + return "", lastErr } // APIEndpoint represents a remote API endpoint From e7f4f04156aa05c1b982d2acc8d60b28db683c52 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 24 Jul 2025 18:49:43 +0200 Subject: [PATCH 017/193] internal/registry: remove PingResponseError It's not matched anywhere, so we can just return a plain error. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit dad2e67860ee4685e46a4664457d071de5b5ec86) Signed-off-by: Sebastiaan van Stijn --- internal/registry/auth.go | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/internal/registry/auth.go b/internal/registry/auth.go index 8799686531aa..ec3f7e6ac823 100644 --- a/internal/registry/auth.go +++ b/internal/registry/auth.go @@ -127,29 +127,19 @@ func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifi }, nil } -// PingResponseError is used when the response from a ping -// was received but invalid. -type PingResponseError struct { - Err error -} - -func (err PingResponseError) Error() string { - return err.Err.Error() -} - // PingV2Registry attempts to ping a v2 registry and on success return a // challenge manager for the supported authentication types. // If a response is received but cannot be interpreted, a PingResponseError will be returned. func PingV2Registry(endpoint *url.URL, authTransport http.RoundTripper) (challenge.Manager, error) { - pingClient := &http.Client{ - Transport: authTransport, - Timeout: 15 * time.Second, - } endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/" req, err := http.NewRequest(http.MethodGet, endpointStr, http.NoBody) if err != nil { return nil, err } + pingClient := &http.Client{ + Transport: authTransport, + Timeout: 15 * time.Second, + } resp, err := pingClient.Do(req) if err != nil { return nil, err @@ -158,9 +148,7 @@ func PingV2Registry(endpoint *url.URL, authTransport http.RoundTripper) (challen challengeManager := challenge.NewSimpleManager() if err := challengeManager.AddResponse(resp); err != nil { - return nil, PingResponseError{ - Err: err, - } + return nil, err } return challengeManager, nil From 9071d3868cc4b2eb44c398445b0f898c4012f903 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 24 Jul 2025 19:05:02 +0200 Subject: [PATCH 018/193] internal/registry: remove NewStaticCredentialStore It was only used in a single place; inline it there. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit dc41365b562062ce32ba912e8cdafeb04f16d5c0) Signed-off-by: Sebastiaan van Stijn --- cli/registry/client/endpoint.go | 23 ++++++++++++++++++++++- internal/registry/auth.go | 29 ----------------------------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/cli/registry/client/endpoint.go b/cli/registry/client/endpoint.go index 1b966e330dbb..4c4ff1d657e6 100644 --- a/cli/registry/client/endpoint.go +++ b/cli/registry/client/endpoint.go @@ -3,6 +3,7 @@ package client import ( "net" "net/http" + "net/url" "time" "github.com/distribution/reference" @@ -97,7 +98,7 @@ func getHTTPTransport(authConfig registrytypes.AuthConfig, endpoint registry.API if len(actions) == 0 { actions = []string{"pull"} } - creds := registry.NewStaticCredentialStore(&authConfig) + creds := &staticCredentialStore{authConfig: &authConfig} tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName, actions...) basicHandler := auth.NewBasicHandler(creds) modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) @@ -117,3 +118,23 @@ func (th *existingTokenHandler) AuthorizeRequest(req *http.Request, _ map[string func (*existingTokenHandler) Scheme() string { return "bearer" } + +type staticCredentialStore struct { + authConfig *registrytypes.AuthConfig +} + +func (scs staticCredentialStore) Basic(*url.URL) (string, string) { + if scs.authConfig == nil { + return "", "" + } + return scs.authConfig.Username, scs.authConfig.Password +} + +func (scs staticCredentialStore) RefreshToken(*url.URL, string) string { + if scs.authConfig == nil { + return "" + } + return scs.authConfig.IdentityToken +} + +func (staticCredentialStore) SetRefreshToken(*url.URL, string, string) {} diff --git a/internal/registry/auth.go b/internal/registry/auth.go index ec3f7e6ac823..18e7c7dd0c66 100644 --- a/internal/registry/auth.go +++ b/internal/registry/auth.go @@ -34,35 +34,6 @@ func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token strin lcs.authConfig.IdentityToken = token } -type staticCredentialStore struct { - auth *registry.AuthConfig -} - -// NewStaticCredentialStore returns a credential store -// which always returns the same credential values. -func NewStaticCredentialStore(ac *registry.AuthConfig) auth.CredentialStore { - return staticCredentialStore{ - auth: ac, - } -} - -func (scs staticCredentialStore) Basic(*url.URL) (string, string) { - if scs.auth == nil { - return "", "" - } - return scs.auth.Username, scs.auth.Password -} - -func (scs staticCredentialStore) RefreshToken(*url.URL, string) string { - if scs.auth == nil { - return "" - } - return scs.auth.IdentityToken -} - -func (staticCredentialStore) SetRefreshToken(*url.URL, string, string) { -} - // loginV2 tries to login to the v2 registry server. The given registry // endpoint will be pinged to get authorization challenges. These challenges // will be used to authenticate against the registry to validate credentials. From 75a4cbbf8e1791152c5936dce591f0f73b69b439 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 24 Jul 2025 20:07:22 +0200 Subject: [PATCH 019/193] internal/registry: remove duplicate endpoint methods now that we no longer need to account for mirrors, these were identical, so just use a single one. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 5322affc9f6f02f51b926ea2e650935c37ff2bd7) Signed-off-by: Sebastiaan van Stijn --- cli/registry/client/endpoint.go | 3 ++- cli/registry/client/fetcher.go | 4 ++-- internal/registry/service.go | 14 +------------- internal/registry/service_v2.go | 27 ++++++++------------------- 4 files changed, 13 insertions(+), 35 deletions(-) diff --git a/cli/registry/client/endpoint.go b/cli/registry/client/endpoint.go index 4c4ff1d657e6..c76197867ac3 100644 --- a/cli/registry/client/endpoint.go +++ b/cli/registry/client/endpoint.go @@ -1,6 +1,7 @@ package client import ( + "context" "net" "net/http" "net/url" @@ -55,7 +56,7 @@ func getDefaultEndpoint(repoName reference.Named, insecure bool) (registry.APIEn if err != nil { return registry.APIEndpoint{}, err } - endpoints, err := registryService.LookupPushEndpoints(reference.Domain(repoName)) + endpoints, err := registryService.Endpoints(context.TODO(), reference.Domain(repoName)) if err != nil { return registry.APIEndpoint{}, err } diff --git a/cli/registry/client/fetcher.go b/cli/registry/client/fetcher.go index e169179d1670..c8687d9d91c8 100644 --- a/cli/registry/client/fetcher.go +++ b/cli/registry/client/fetcher.go @@ -283,10 +283,10 @@ func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoi } registryService, err := registry.NewService(serviceOpts) if err != nil { - return []registry.APIEndpoint{}, err + return nil, err } repoInfo, _ := registry.ParseRepositoryInfo(namedRef) - endpoints, err := registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name)) + endpoints, err := registryService.Endpoints(context.TODO(), reference.Domain(repoInfo.Name)) logrus.Debugf("endpoints for %s: %v", namedRef, endpoints) return endpoints, err } diff --git a/internal/registry/service.go b/internal/registry/service.go index 36e95d8c63d8..8f908c73a0ce 100644 --- a/internal/registry/service.go +++ b/internal/registry/service.go @@ -49,7 +49,7 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use } // Lookup endpoints for authentication. - endpoints, err := s.lookupV2Endpoints(ctx, registryHostName) + endpoints, err := s.Endpoints(ctx, registryHostName) if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return "", err @@ -85,15 +85,3 @@ type APIEndpoint struct { URL *url.URL TLSConfig *tls.Config } - -// LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference. -// It gives preference to mirrors over the actual registry, and HTTPS over plain HTTP. -func (s *Service) LookupPullEndpoints(hostname string) ([]APIEndpoint, error) { - return s.lookupV2Endpoints(context.TODO(), hostname) -} - -// LookupPushEndpoints creates a list of v2 endpoints to try to push to, in order of preference. -// It gives preference to HTTPS over plain HTTP. Mirrors are not included. -func (s *Service) LookupPushEndpoints(hostname string) ([]APIEndpoint, error) { - return s.lookupV2Endpoints(context.TODO(), hostname) -} diff --git a/internal/registry/service_v2.go b/internal/registry/service_v2.go index 0f0059533022..ccfb5ac50959 100644 --- a/internal/registry/service_v2.go +++ b/internal/registry/service_v2.go @@ -7,15 +7,12 @@ import ( "github.com/docker/go-connections/tlsconfig" ) -func (s *Service) lookupV2Endpoints(ctx context.Context, hostname string) ([]APIEndpoint, error) { - var endpoints []APIEndpoint +func (s *Service) Endpoints(ctx context.Context, hostname string) ([]APIEndpoint, error) { if hostname == DefaultNamespace || hostname == IndexHostname { - endpoints = append(endpoints, APIEndpoint{ + return []APIEndpoint{{ URL: DefaultV2Registry, TLSConfig: tlsconfig.ServerDefault(), - }) - - return endpoints, nil + }}, nil } tlsConfig, err := newTLSConfig(ctx, hostname, s.config.isSecureIndex(hostname)) @@ -23,22 +20,14 @@ func (s *Service) lookupV2Endpoints(ctx context.Context, hostname string) ([]API return nil, err } - endpoints = []APIEndpoint{ - { - URL: &url.URL{ - Scheme: "https", - Host: hostname, - }, - TLSConfig: tlsConfig, - }, - } + endpoints := []APIEndpoint{{ + URL: &url.URL{Scheme: "https", Host: hostname}, + TLSConfig: tlsConfig, + }} if tlsConfig.InsecureSkipVerify { endpoints = append(endpoints, APIEndpoint{ - URL: &url.URL{ - Scheme: "http", - Host: hostname, - }, + URL: &url.URL{Scheme: "http", Host: hostname}, // used to check if supposed to be secure via InsecureSkipVerify TLSConfig: tlsConfig, }) From cab6012db9686c75b28f23d579a6eb38fc020a02 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 24 Jul 2025 22:15:08 +0200 Subject: [PATCH 020/193] internal/registry: remove ValidateIndexName It was written to be used as validate-func for command-line flags, which we don't use it for (which for CLI-flags includes normalizing the value). The validation itself didn't add much; it only checked the registry didn't start or end with a hyphen (which would still fail when parsing). Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 2607ba8062b3fccd21b4ffe7c2ec3a27911bc3b5) Signed-off-by: Sebastiaan van Stijn --- internal/registry/config.go | 14 ------- internal/registry/config_test.go | 65 -------------------------------- 2 files changed, 79 deletions(-) diff --git a/internal/registry/config.go b/internal/registry/config.go index 78a2e8f52070..18add46118e8 100644 --- a/internal/registry/config.go +++ b/internal/registry/config.go @@ -105,10 +105,6 @@ func (config *serviceConfig) loadInsecureRegistries(registries []string) error { skip: for _, r := range registries { - // validate insecure registry - if _, err := ValidateIndexName(r); err != nil { - return err - } if scheme, host, ok := strings.Cut(r, "://"); ok { switch strings.ToLower(scheme) { case "http", "https": @@ -219,16 +215,6 @@ func isCIDRMatch(cidrs []*registry.NetIPNet, urlHost string) bool { return false } -// ValidateIndexName validates an index name. It is used by the daemon to -// validate the daemon configuration. -func ValidateIndexName(val string) (string, error) { - val = normalizeIndexName(val) - if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") { - return "", invalidParamf("invalid index name (%s). Cannot begin or end with a hyphen", val) - } - return val, nil -} - func normalizeIndexName(val string) string { if val == "index.docker.io" { return "docker.io" diff --git a/internal/registry/config_test.go b/internal/registry/config_test.go index f3d8b4c44a2e..7e0838fe59c6 100644 --- a/internal/registry/config_test.go +++ b/internal/registry/config_test.go @@ -5,7 +5,6 @@ import ( cerrdefs "github.com/containerd/errdefs" "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" ) func TestLoadInsecureRegistries(t *testing.T) { @@ -46,10 +45,6 @@ func TestLoadInsecureRegistries(t *testing.T) { registries: []string{"svn://myregistry.example.com"}, err: "insecure registry svn://myregistry.example.com should not contain '://'", }, - { - registries: []string{"-invalid-registry"}, - err: "Cannot begin or end with a hyphen", - }, { registries: []string{`mytest-.com`}, err: `insecure registry mytest-.com is not valid: invalid host "mytest-.com"`, @@ -96,63 +91,3 @@ func TestLoadInsecureRegistries(t *testing.T) { } } } - -func TestValidateIndexName(t *testing.T) { - valid := []struct { - index string - expect string - }{ - { - index: "index.docker.io", - expect: "docker.io", - }, - { - index: "example.com", - expect: "example.com", - }, - { - index: "127.0.0.1:8080", - expect: "127.0.0.1:8080", - }, - { - index: "mytest-1.com", - expect: "mytest-1.com", - }, - { - index: "mirror-1.example.com/v1/?q=foo", - expect: "mirror-1.example.com/v1/?q=foo", - }, - } - - for _, testCase := range valid { - result, err := ValidateIndexName(testCase.index) - if assert.Check(t, err) { - assert.Check(t, is.Equal(testCase.expect, result)) - } - } -} - -func TestValidateIndexNameWithError(t *testing.T) { - invalid := []struct { - index string - err string - }{ - { - index: "docker.io-", - err: "invalid index name (docker.io-). Cannot begin or end with a hyphen", - }, - { - index: "-example.com", - err: "invalid index name (-example.com). Cannot begin or end with a hyphen", - }, - { - index: "mirror-1.example.com/v1/?q=foo-", - err: "invalid index name (mirror-1.example.com/v1/?q=foo-). Cannot begin or end with a hyphen", - }, - } - for _, testCase := range invalid { - _, err := ValidateIndexName(testCase.index) - assert.Check(t, is.Error(err, testCase.err)) - assert.Check(t, cerrdefs.IsInvalidArgument(err)) - } -} From e1f79927f34c97035f3cda551fddcb6f8723dce6 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 24 Jul 2025 22:36:47 +0200 Subject: [PATCH 021/193] internal/registry: define local serviceConfig The registry.ServiceConfig struct in the API types was meant for the registry configuration on the daemon side; it has variuos fields we don't use, defines methods for (un)marshaling JSON, and a custom version of `net.IPNet`, also to (un)marshal JSON. None of that is needed, so let's change it to a local type, and implement a constructor (as we now only have "insecure registries" to care about). Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 219cfc8b7da53ec0e725a0ce31368d13f7dd8f5f) Signed-off-by: Sebastiaan van Stijn --- internal/registry/config.go | 63 +++++++++++++++++--------------- internal/registry/config_test.go | 7 ++-- internal/registry/service.go | 8 ++-- 3 files changed, 40 insertions(+), 38 deletions(-) diff --git a/internal/registry/config.go b/internal/registry/config.go index 18add46118e8..d7d160ef7fbb 100644 --- a/internal/registry/config.go +++ b/internal/registry/config.go @@ -21,12 +21,19 @@ import ( ) // ServiceOptions holds command line options. +// +// TODO(thaJeztah): add CertsDir as option to replace the [CertsDir] function, which sets the location magically. type ServiceOptions struct { InsecureRegistries []string `json:"insecure-registries,omitempty"` } // serviceConfig holds daemon configuration for the registry service. -type serviceConfig registry.ServiceConfig +// +// It's a reduced version of [registry.ServiceConfig] for the CLI. +type serviceConfig struct { + insecureRegistryCIDRs []*net.IPNet + indexConfigs map[string]*registry.IndexInfo +} // TODO(thaJeztah) both the "index.docker.io" and "registry-1.docker.io" domains // are here for historic reasons and backward-compatibility. These domains @@ -38,27 +45,22 @@ type serviceConfig registry.ServiceConfig const ( // DefaultNamespace is the default namespace DefaultNamespace = "docker.io" - // DefaultRegistryHost is the hostname for the default (Docker Hub) registry - // used for pushing and pulling images. This hostname is hard-coded to handle - // the conversion from image references without registry name (e.g. "ubuntu", - // or "ubuntu:latest"), as well as references using the "docker.io" domain - // name, which is used as canonical reference for images on Docker Hub, but - // does not match the domain-name of Docker Hub's registry. - DefaultRegistryHost = "registry-1.docker.io" // IndexHostname is the index hostname, used for authentication and image search. IndexHostname = "index.docker.io" // IndexServer is used for user auth and image search - IndexServer = "https://" + IndexHostname + "/v1/" + IndexServer = "https://index.docker.io/v1/" // IndexName is the name of the index IndexName = "docker.io" ) var ( - // DefaultV2Registry is the URI of the default (Docker Hub) registry. - DefaultV2Registry = &url.URL{ - Scheme: "https", - Host: DefaultRegistryHost, - } + // DefaultV2Registry is the URI of the default (Docker Hub) registry + // used for pushing and pulling images. This hostname is hard-coded to handle + // the conversion from image references without registry name (e.g. "ubuntu", + // or "ubuntu:latest"), as well as references using the "docker.io" domain + // name, which is used as canonical reference for images on Docker Hub, but + // does not match the domain-name of Docker Hub's registry. + DefaultV2Registry = &url.URL{Scheme: "https", Host: "registry-1.docker.io"} validHostPortRegex = sync.OnceValue(func() *regexp.Regexp { return regexp.MustCompile(`^` + reference.DomainRegexp.String() + `$`) @@ -92,14 +94,17 @@ func CertsDir() string { return certsDir } -// loadInsecureRegistries loads insecure registries to config -func (config *serviceConfig) loadInsecureRegistries(registries []string) error { +// newServiceConfig creates a new service config with the given options. +func newServiceConfig(registries []string) (*serviceConfig, error) { + if len(registries) == 0 { + return &serviceConfig{}, nil + } // Localhost is by default considered as an insecure registry. This is a // stop-gap for people who are running a private registry on localhost. registries = append(registries, "::1/128", "127.0.0.0/8") var ( - insecureRegistryCIDRs = make([]*registry.NetIPNet, 0) + insecureRegistryCIDRs = make([]*net.IPNet, 0) indexConfigs = make(map[string]*registry.IndexInfo) ) @@ -112,24 +117,23 @@ skip: r = host default: // unsupported scheme - return invalidParamf("insecure registry %s should not contain '://'", r) + return nil, invalidParamf("insecure registry %s should not contain '://'", r) } } // Check if CIDR was passed to --insecure-registry _, ipnet, err := net.ParseCIDR(r) if err == nil { // Valid CIDR. If ipnet is already in config.InsecureRegistryCIDRs, skip. - data := (*registry.NetIPNet)(ipnet) for _, value := range insecureRegistryCIDRs { - if value.IP.String() == data.IP.String() && value.Mask.String() == data.Mask.String() { + if value.IP.String() == ipnet.IP.String() && value.Mask.String() == ipnet.Mask.String() { continue skip } } // ipnet is not found, add it in config.InsecureRegistryCIDRs - insecureRegistryCIDRs = append(insecureRegistryCIDRs, data) + insecureRegistryCIDRs = append(insecureRegistryCIDRs, ipnet) } else { if err := validateHostPort(r); err != nil { - return invalidParamWrapf(err, "insecure registry %s is not valid", r) + return nil, invalidParamWrapf(err, "insecure registry %s is not valid", r) } // Assume `host:port` if not CIDR. indexConfigs[r] = ®istry.IndexInfo{ @@ -146,10 +150,11 @@ skip: Secure: true, Official: true, } - config.InsecureRegistryCIDRs = insecureRegistryCIDRs - config.IndexConfigs = indexConfigs - return nil + return &serviceConfig{ + indexConfigs: indexConfigs, + insecureRegistryCIDRs: insecureRegistryCIDRs, + }, nil } // isSecureIndex returns false if the provided indexName is part of the list of insecure registries @@ -166,11 +171,11 @@ skip: func (config *serviceConfig) isSecureIndex(indexName string) bool { // Check for configured index, first. This is needed in case isSecureIndex // is called from anything besides newIndexInfo, in order to honor per-index configurations. - if index, ok := config.IndexConfigs[indexName]; ok { + if index, ok := config.indexConfigs[indexName]; ok { return index.Secure } - return !isCIDRMatch(config.InsecureRegistryCIDRs, indexName) + return !isCIDRMatch(config.insecureRegistryCIDRs, indexName) } // for mocking in unit tests. @@ -179,7 +184,7 @@ var lookupIP = net.LookupIP // isCIDRMatch returns true if urlHost matches an element of cidrs. urlHost is a URL.Host ("host:port" or "host") // where the `host` part can be either a domain name or an IP address. If it is a domain name, then it will be // resolved to IP addresses for matching. If resolution fails, false is returned. -func isCIDRMatch(cidrs []*registry.NetIPNet, urlHost string) bool { +func isCIDRMatch(cidrs []*net.IPNet, urlHost string) bool { if len(cidrs) == 0 { return false } @@ -206,7 +211,7 @@ func isCIDRMatch(cidrs []*registry.NetIPNet, urlHost string) bool { for _, addr := range addresses { for _, ipnet := range cidrs { // check if the addr falls in the subnet - if (*net.IPNet)(ipnet).Contains(addr) { + if ipnet.Contains(addr) { return true } } diff --git a/internal/registry/config_test.go b/internal/registry/config_test.go index 7e0838fe59c6..a5574a298d45 100644 --- a/internal/registry/config_test.go +++ b/internal/registry/config_test.go @@ -67,20 +67,19 @@ func TestLoadInsecureRegistries(t *testing.T) { }, } for _, testCase := range testCases { - config := &serviceConfig{} - err := config.loadInsecureRegistries(testCase.registries) + config, err := newServiceConfig(testCase.registries) if testCase.err == "" { if err != nil { t.Fatalf("expect no error, got '%s'", err) } match := false - for index := range config.IndexConfigs { + for index := range config.indexConfigs { if index == testCase.index { match = true } } if !match { - t.Fatalf("expect index configs to contain '%s', got %+v", testCase.index, config.IndexConfigs) + t.Fatalf("expect index configs to contain '%s', got %+v", testCase.index, config.indexConfigs) } } else { if err == nil { diff --git a/internal/registry/service.go b/internal/registry/service.go index 8f908c73a0ce..60622fba5f16 100644 --- a/internal/registry/service.go +++ b/internal/registry/service.go @@ -21,11 +21,9 @@ type Service struct { // NewService returns a new instance of [Service] ready to be installed into // an engine. func NewService(options ServiceOptions) (*Service, error) { - config := &serviceConfig{} - if len(options.InsecureRegistries) > 0 { - if err := config.loadInsecureRegistries(options.InsecureRegistries); err != nil { - return nil, err - } + config, err := newServiceConfig(options.InsecureRegistries) + if err != nil { + return nil, err } return &Service{config: config}, nil } From a5a17eb2c709d19d1a39fde8f4f617be871f522b Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 24 Jul 2025 23:15:40 +0200 Subject: [PATCH 022/193] internal/registry: remove pkg/errors Signed-off-by: Sebastiaan van Stijn (cherry picked from commit c297770d2df1d4438d0e0b5f3d3b57b293c96ffa) Signed-off-by: Sebastiaan van Stijn --- internal/registry/auth.go | 4 ++-- internal/registry/config.go | 5 +++-- internal/registry/errors.go | 9 +++------ internal/registry/registry.go | 3 ++- internal/registry/service.go | 3 ++- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/registry/auth.go b/internal/registry/auth.go index 18e7c7dd0c66..4a042c9a17c4 100644 --- a/internal/registry/auth.go +++ b/internal/registry/auth.go @@ -2,6 +2,7 @@ package registry import ( "context" + "fmt" "net/http" "net/url" "strings" @@ -12,7 +13,6 @@ import ( "github.com/docker/distribution/registry/client/auth/challenge" "github.com/docker/distribution/registry/client/transport" "github.com/docker/docker/api/types/registry" - "github.com/pkg/errors" ) // AuthClientID is used the ClientID used for the token server @@ -67,7 +67,7 @@ func loginV2(ctx context.Context, authConfig *registry.AuthConfig, endpoint APIE if resp.StatusCode != http.StatusOK { // TODO(dmcgowan): Attempt to further interpret result, status code and error code string - return "", errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode)) + return "", fmt.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode)) } return credentialAuthConfig.IdentityToken, nil diff --git a/internal/registry/config.go b/internal/registry/config.go index d7d160ef7fbb..89ed4e0d3ce4 100644 --- a/internal/registry/config.go +++ b/internal/registry/config.go @@ -5,6 +5,7 @@ package registry import ( "context" + "fmt" "net" "net/url" "os" @@ -117,7 +118,7 @@ skip: r = host default: // unsupported scheme - return nil, invalidParamf("insecure registry %s should not contain '://'", r) + return nil, invalidParam(fmt.Errorf("insecure registry %s should not contain '://'", r)) } } // Check if CIDR was passed to --insecure-registry @@ -133,7 +134,7 @@ skip: insecureRegistryCIDRs = append(insecureRegistryCIDRs, ipnet) } else { if err := validateHostPort(r); err != nil { - return nil, invalidParamWrapf(err, "insecure registry %s is not valid", r) + return nil, invalidParam(fmt.Errorf("insecure registry %s is not valid: %w", r, err)) } // Assume `host:port` if not CIDR. indexConfigs[r] = ®istry.IndexInfo{ diff --git a/internal/registry/errors.go b/internal/registry/errors.go index 4174c91d15b4..9b321ab0159d 100644 --- a/internal/registry/errors.go +++ b/internal/registry/errors.go @@ -1,10 +1,11 @@ package registry import ( + "errors" + "fmt" "net/url" "github.com/docker/distribution/registry/api/errcode" - "github.com/pkg/errors" ) func translateV2AuthError(err error) error { @@ -23,11 +24,7 @@ func invalidParam(err error) error { } func invalidParamf(format string, args ...interface{}) error { - return invalidParameterErr{errors.Errorf(format, args...)} -} - -func invalidParamWrapf(err error, format string, args ...interface{}) error { - return invalidParameterErr{errors.Wrapf(err, format, args...)} + return invalidParameterErr{fmt.Errorf(format, args...)} } type unauthorizedErr struct{ error } diff --git a/internal/registry/registry.go b/internal/registry/registry.go index d69c1d9ec7b3..97946d7fd879 100644 --- a/internal/registry/registry.go +++ b/internal/registry/registry.go @@ -4,6 +4,7 @@ package registry import ( "context" "crypto/tls" + "fmt" "net" "net/http" "os" @@ -82,7 +83,7 @@ func loadTLSConfig(ctx context.Context, directory string, tlsConfig *tls.Config) if tlsConfig.RootCAs == nil { systemPool, err := tlsconfig.SystemCertPool() if err != nil { - return invalidParamWrapf(err, "unable to get system cert pool") + return invalidParam(fmt.Errorf("unable to get system cert pool: %w", err)) } tlsConfig.RootCAs = systemPool } diff --git a/internal/registry/service.go b/internal/registry/service.go index 60622fba5f16..1936a2972a1f 100644 --- a/internal/registry/service.go +++ b/internal/registry/service.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "errors" + "fmt" "net/url" "strings" @@ -41,7 +42,7 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use } u, err := url.Parse(serverAddress) if err != nil { - return "", invalidParamWrapf(err, "unable to parse server address") + return "", invalidParam(fmt.Errorf("unable to parse server address: %w", err)) } registryHostName = u.Host } From 2e5a36728bc9bd47baae2dc1e0f16423f782fffb Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 24 Jul 2025 23:24:22 +0200 Subject: [PATCH 023/193] cli/command/system: remove use of Mirrors field in test Signed-off-by: Sebastiaan van Stijn (cherry picked from commit cd277a5815ace9b7de0e116c8d243a13e2105213) Signed-off-by: Sebastiaan van Stijn --- cli/command/system/info_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/command/system/info_test.go b/cli/command/system/info_test.go index d996ea23f49a..726d1ea5a2d4 100644 --- a/cli/command/system/info_test.go +++ b/cli/command/system/info_test.go @@ -78,7 +78,6 @@ var sampleInfoNoSwarm = system.Info{ IndexConfigs: map[string]*registrytypes.IndexInfo{ "docker.io": { Name: "docker.io", - Mirrors: nil, Secure: true, Official: true, }, From b0bb4acc112a0bf2e57e713d7cfcbab469cc43c0 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 25 Jul 2025 00:08:26 +0200 Subject: [PATCH 024/193] internal/registry: fix linting issues (revive) internal/registry/errors.go:26:43: use-any: since Go 1.18 'interface{}' can be replaced by 'any' (revive) func invalidParamf(format string, args ...interface{}) error { ^ internal/registry/registry_mock_test.go:52:51: use-any: since Go 1.18 'interface{}' can be replaced by 'any' (revive) func writeResponse(w http.ResponseWriter, message interface{}, code int) { ^ Signed-off-by: Sebastiaan van Stijn (cherry picked from commit f907c7a4b03898455d024920f38fef3545a29c05) Signed-off-by: Sebastiaan van Stijn --- internal/registry/errors.go | 5 ++++- internal/registry/registry_mock_test.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/registry/errors.go b/internal/registry/errors.go index 9b321ab0159d..e27eb3e7a682 100644 --- a/internal/registry/errors.go +++ b/internal/registry/errors.go @@ -1,3 +1,6 @@ +// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: +//go:build go1.23 + package registry import ( @@ -23,7 +26,7 @@ func invalidParam(err error) error { return invalidParameterErr{err} } -func invalidParamf(format string, args ...interface{}) error { +func invalidParamf(format string, args ...any) error { return invalidParameterErr{fmt.Errorf(format, args...)} } diff --git a/internal/registry/registry_mock_test.go b/internal/registry/registry_mock_test.go index 6ccabce5bcaf..13c637852eb2 100644 --- a/internal/registry/registry_mock_test.go +++ b/internal/registry/registry_mock_test.go @@ -49,7 +49,7 @@ func writeHeaders(w http.ResponseWriter) { h.Add("Cache-Control", "no-cache") } -func writeResponse(w http.ResponseWriter, message interface{}, code int) { +func writeResponse(w http.ResponseWriter, message any, code int) { writeHeaders(w) w.WriteHeader(code) body, err := json.Marshal(message) From 28ffe2416d474700b0ab5a75e6ee71a0b1742364 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 25 Jul 2025 23:50:41 +0200 Subject: [PATCH 025/193] internal/registry: ParseRepositoryInfo: remove unused error return Removed the error return from the `ParseRepositoryInfo` function. There are no validation steps inside `ParseRepositoryInfo` which could cause an error, so we always returned a nil error. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 86b5b528a60e65e580fb2baf8f0d3401bacb076d) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/push.go | 2 +- cli/command/plugin/install.go | 3 +-- cli/command/plugin/push.go | 2 +- cli/command/service/trust.go | 2 +- cli/registry/client/endpoint.go | 2 +- cli/registry/client/fetcher.go | 4 ++-- cli/trust/trust.go | 2 +- internal/registry/config.go | 8 +++----- internal/registry/registry_test.go | 3 +-- 9 files changed, 12 insertions(+), 16 deletions(-) diff --git a/cli/command/image/push.go b/cli/command/image/push.go index ff2bb39b91ed..da036af8ee18 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -105,7 +105,7 @@ To push the complete multi-platform image, remove the --platform flag. } // Resolve the Repository name from fqn to RepositoryInfo - repoInfo, _ := registry.ParseRepositoryInfo(ref) + repoInfo := registry.ParseRepositoryInfo(ref) // Resolve the Auth config relevant for this server authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), repoInfo.Index) diff --git a/cli/command/plugin/install.go b/cli/command/plugin/install.go index a737f8d9c25e..9626d3c647f4 100644 --- a/cli/command/plugin/install.go +++ b/cli/command/plugin/install.go @@ -65,8 +65,7 @@ func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOpti return types.PluginInstallOptions{}, err } - repoInfo, _ := registry.ParseRepositoryInfo(ref) - + repoInfo := registry.ParseRepositoryInfo(ref) remote := ref.String() _, isCanonical := ref.(reference.Canonical) diff --git a/cli/command/plugin/push.go b/cli/command/plugin/push.go index 30e9c7f5d1c2..36fcb79644b5 100644 --- a/cli/command/plugin/push.go +++ b/cli/command/plugin/push.go @@ -49,7 +49,7 @@ func runPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error named = reference.TagNameOnly(named) - repoInfo, _ := registry.ParseRepositoryInfo(named) + repoInfo := registry.ParseRepositoryInfo(named) authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), repoInfo.Index) encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig) if err != nil { diff --git a/cli/command/service/trust.go b/cli/command/service/trust.go index 49c65d668d71..4b6691234974 100644 --- a/cli/command/service/trust.go +++ b/cli/command/service/trust.go @@ -51,7 +51,7 @@ func resolveServiceImageDigestContentTrust(dockerCli command.Cli, service *swarm } func trustedResolveDigest(cli command.Cli, ref reference.NamedTagged) (reference.Canonical, error) { - repoInfo, _ := registry.ParseRepositoryInfo(ref) + repoInfo := registry.ParseRepositoryInfo(ref) authConfig := command.ResolveAuthConfig(cli.ConfigFile(), repoInfo.Index) notaryRepo, err := trust.GetNotaryRepository(cli.In(), cli.Out(), command.UserAgent(), repoInfo, &authConfig, "pull") diff --git a/cli/registry/client/endpoint.go b/cli/registry/client/endpoint.go index c76197867ac3..4a1e8b524795 100644 --- a/cli/registry/client/endpoint.go +++ b/cli/registry/client/endpoint.go @@ -34,7 +34,7 @@ func (r repositoryEndpoint) BaseURL() string { func newDefaultRepositoryEndpoint(ref reference.Named, insecure bool) (repositoryEndpoint, error) { repoName := reference.TrimNamed(ref) - repoInfo, _ := registry.ParseRepositoryInfo(ref) + repoInfo := registry.ParseRepositoryInfo(ref) indexInfo := repoInfo.Index endpoint, err := getDefaultEndpoint(ref, !indexInfo.Secure) diff --git a/cli/registry/client/fetcher.go b/cli/registry/client/fetcher.go index c8687d9d91c8..28fa4f89ece5 100644 --- a/cli/registry/client/fetcher.go +++ b/cli/registry/client/fetcher.go @@ -221,7 +221,7 @@ func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, } repoName := reference.TrimNamed(namedRef) - repoInfo, _ := registry.ParseRepositoryInfo(namedRef) + repoInfo := registry.ParseRepositoryInfo(namedRef) indexInfo := repoInfo.Index confirmedTLSRegistries := make(map[string]bool) @@ -285,7 +285,7 @@ func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoi if err != nil { return nil, err } - repoInfo, _ := registry.ParseRepositoryInfo(namedRef) + repoInfo := registry.ParseRepositoryInfo(namedRef) endpoints, err := registryService.Endpoints(context.TODO(), reference.Domain(repoInfo.Name)) logrus.Debugf("endpoints for %s: %v", namedRef, endpoints) return endpoints, err diff --git a/cli/trust/trust.go b/cli/trust/trust.go index abfc8fdb5f48..647c3243a5af 100644 --- a/cli/trust/trust.go +++ b/cli/trust/trust.go @@ -321,7 +321,7 @@ func GetImageReferencesAndAuth(ctx context.Context, } // Resolve the Repository name from fqn to RepositoryInfo - repoInfo, _ := registry.ParseRepositoryInfo(ref) + repoInfo := registry.ParseRepositoryInfo(ref) authConfig := authResolver(ctx, repoInfo.Index) return ImageRefAndAuth{ original: imgName, diff --git a/internal/registry/config.go b/internal/registry/config.go index 89ed4e0d3ce4..f2f026c5b9e5 100644 --- a/internal/registry/config.go +++ b/internal/registry/config.go @@ -254,9 +254,7 @@ func validateHostPort(s string) error { // ParseRepositoryInfo performs the breakdown of a repository name into a // [RepositoryInfo], but lacks registry configuration. -// -// It is used by the Docker cli to interact with registry-related endpoints. -func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) { +func ParseRepositoryInfo(reposName reference.Named) *RepositoryInfo { indexName := normalizeIndexName(reference.Domain(reposName)) if indexName == IndexName { return &RepositoryInfo{ @@ -266,7 +264,7 @@ func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) { Secure: true, Official: true, }, - }, nil + } } return &RepositoryInfo{ @@ -275,7 +273,7 @@ func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) { Name: indexName, Secure: !isInsecure(indexName), }, - }, nil + } } // isInsecure is used to detect whether a registry domain or IP-address is allowed diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go index f0b061b23fe9..e7cbef3a0b19 100644 --- a/internal/registry/registry_test.go +++ b/internal/registry/registry_test.go @@ -269,8 +269,7 @@ func TestParseRepositoryInfo(t *testing.T) { named, err := reference.ParseNormalizedNamed(reposName) assert.NilError(t, err) - repoInfo, err := ParseRepositoryInfo(named) - assert.NilError(t, err) + repoInfo := ParseRepositoryInfo(named) assert.Check(t, is.DeepEqual(repoInfo.Index, expected.Index)) assert.Check(t, is.Equal(reference.Path(repoInfo.Name), expected.RemoteName)) From efdf008933cab0e767b5eea5741d37d06d7613d8 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 3 Aug 2025 14:39:30 +0200 Subject: [PATCH 026/193] internal/registry: remove RepositoryInfo, add NewIndexInfo Most places only use IndexInfo (and may not even need that), so replace the use of ParseRepositoryInfo for NewIndexInfo, and move the RepositoryInfo type to the trust package, which uses it as part of its ImageRefAndAuth struct. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 21e8bbc8a28c8ca1c1ec139c4397f7b45462d064) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/push.go | 6 +++--- cli/command/image/trust.go | 7 +++++-- cli/command/plugin/install.go | 4 ++-- cli/command/plugin/push.go | 8 ++++++-- cli/command/service/trust.go | 9 ++++++--- cli/registry/client/endpoint.go | 3 +-- cli/registry/client/fetcher.go | 6 ++---- cli/trust/trust.go | 26 ++++++++++++++++++-------- cli/trust/trust_push.go | 3 +-- internal/registry/config.go | 27 +++++++++++---------------- internal/registry/registry_test.go | 13 +++++++------ internal/registry/types.go | 13 ------------- 12 files changed, 62 insertions(+), 63 deletions(-) delete mode 100644 internal/registry/types.go diff --git a/cli/command/image/push.go b/cli/command/image/push.go index da036af8ee18..cd79ff92894a 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -105,10 +105,10 @@ To push the complete multi-platform image, remove the --platform flag. } // Resolve the Repository name from fqn to RepositoryInfo - repoInfo := registry.ParseRepositoryInfo(ref) + indexInfo := registry.NewIndexInfo(ref) // Resolve the Auth config relevant for this server - authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), repoInfo.Index) + authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), indexInfo) encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig) if err != nil { return err @@ -134,7 +134,7 @@ To push the complete multi-platform image, remove the --platform flag. defer responseBody.Close() if !opts.untrusted { // TODO pushTrustedReference currently doesn't respect `--quiet` - return pushTrustedReference(ctx, dockerCli, repoInfo, ref, authConfig, responseBody) + return pushTrustedReference(ctx, dockerCli, indexInfo, ref, authConfig, responseBody) } if opts.quiet { diff --git a/cli/command/image/trust.go b/cli/command/image/trust.go index e0288b9cba98..00268e7592da 100644 --- a/cli/command/image/trust.go +++ b/cli/command/image/trust.go @@ -11,7 +11,6 @@ import ( "github.com/docker/cli/cli/streams" "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/jsonstream" - "github.com/docker/cli/internal/registry" "github.com/docker/docker/api/types/image" registrytypes "github.com/docker/docker/api/types/registry" "github.com/opencontainers/go-digest" @@ -42,7 +41,11 @@ func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth) ( } // pushTrustedReference pushes a canonical reference to the trust server. -func pushTrustedReference(ctx context.Context, ioStreams command.Streams, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader) error { +func pushTrustedReference(ctx context.Context, ioStreams command.Streams, indexInfo *registrytypes.IndexInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader) error { + repoInfo := &trust.RepositoryInfo{ + Name: reference.TrimNamed(ref), + Index: indexInfo, + } return trust.PushTrustedReference(ctx, ioStreams, repoInfo, ref, authConfig, in, command.UserAgent()) } diff --git a/cli/command/plugin/install.go b/cli/command/plugin/install.go index 9626d3c647f4..33a7179edd17 100644 --- a/cli/command/plugin/install.go +++ b/cli/command/plugin/install.go @@ -65,7 +65,7 @@ func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOpti return types.PluginInstallOptions{}, err } - repoInfo := registry.ParseRepositoryInfo(ref) + indexInfo := registry.NewIndexInfo(ref) remote := ref.String() _, isCanonical := ref.(reference.Canonical) @@ -83,7 +83,7 @@ func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOpti remote = reference.FamiliarString(trusted) } - authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), repoInfo.Index) + authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), indexInfo) encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig) if err != nil { return types.PluginInstallOptions{}, err diff --git a/cli/command/plugin/push.go b/cli/command/plugin/push.go index 36fcb79644b5..573ae3d0ae99 100644 --- a/cli/command/plugin/push.go +++ b/cli/command/plugin/push.go @@ -49,8 +49,8 @@ func runPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error named = reference.TagNameOnly(named) - repoInfo := registry.ParseRepositoryInfo(named) - authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), repoInfo.Index) + indexInfo := registry.NewIndexInfo(named) + authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), indexInfo) encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig) if err != nil { return err @@ -63,6 +63,10 @@ func runPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error defer responseBody.Close() if !opts.untrusted { + repoInfo := &trust.RepositoryInfo{ + Name: reference.TrimNamed(named), + Index: indexInfo, + } return trust.PushTrustedReference(ctx, dockerCli, repoInfo, named, authConfig, responseBody, command.UserAgent()) } diff --git a/cli/command/service/trust.go b/cli/command/service/trust.go index 4b6691234974..ea79161ad55f 100644 --- a/cli/command/service/trust.go +++ b/cli/command/service/trust.go @@ -51,9 +51,12 @@ func resolveServiceImageDigestContentTrust(dockerCli command.Cli, service *swarm } func trustedResolveDigest(cli command.Cli, ref reference.NamedTagged) (reference.Canonical, error) { - repoInfo := registry.ParseRepositoryInfo(ref) - authConfig := command.ResolveAuthConfig(cli.ConfigFile(), repoInfo.Index) - + indexInfo := registry.NewIndexInfo(ref) + authConfig := command.ResolveAuthConfig(cli.ConfigFile(), indexInfo) + repoInfo := &trust.RepositoryInfo{ + Name: reference.TrimNamed(ref), + Index: indexInfo, + } notaryRepo, err := trust.GetNotaryRepository(cli.In(), cli.Out(), command.UserAgent(), repoInfo, &authConfig, "pull") if err != nil { return nil, errors.Wrap(err, "error establishing connection to trust repository") diff --git a/cli/registry/client/endpoint.go b/cli/registry/client/endpoint.go index 4a1e8b524795..ce6e774969bd 100644 --- a/cli/registry/client/endpoint.go +++ b/cli/registry/client/endpoint.go @@ -34,8 +34,7 @@ func (r repositoryEndpoint) BaseURL() string { func newDefaultRepositoryEndpoint(ref reference.Named, insecure bool) (repositoryEndpoint, error) { repoName := reference.TrimNamed(ref) - repoInfo := registry.ParseRepositoryInfo(ref) - indexInfo := repoInfo.Index + indexInfo := registry.NewIndexInfo(ref) endpoint, err := getDefaultEndpoint(ref, !indexInfo.Secure) if err != nil { diff --git a/cli/registry/client/fetcher.go b/cli/registry/client/fetcher.go index 28fa4f89ece5..852bc86e1ea4 100644 --- a/cli/registry/client/fetcher.go +++ b/cli/registry/client/fetcher.go @@ -221,8 +221,7 @@ func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, } repoName := reference.TrimNamed(namedRef) - repoInfo := registry.ParseRepositoryInfo(namedRef) - indexInfo := repoInfo.Index + indexInfo := registry.NewIndexInfo(namedRef) confirmedTLSRegistries := make(map[string]bool) for _, endpoint := range endpoints { @@ -285,8 +284,7 @@ func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoi if err != nil { return nil, err } - repoInfo := registry.ParseRepositoryInfo(namedRef) - endpoints, err := registryService.Endpoints(context.TODO(), reference.Domain(repoInfo.Name)) + endpoints, err := registryService.Endpoints(context.TODO(), reference.Domain(namedRef)) logrus.Debugf("endpoints for %s: %v", namedRef, endpoints) return endpoints, err } diff --git a/cli/trust/trust.go b/cli/trust/trust.go index 647c3243a5af..eeddc912b331 100644 --- a/cli/trust/trust.go +++ b/cli/trust/trust.go @@ -95,7 +95,7 @@ func (simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {} // GetNotaryRepository returns a NotaryRepository which stores all the // information needed to operate on a notary repository. // It creates an HTTP transport providing authentication support. -func GetNotaryRepository(in io.Reader, out io.Writer, userAgent string, repoInfo *registry.RepositoryInfo, authConfig *registrytypes.AuthConfig, actions ...string) (client.Repository, error) { +func GetNotaryRepository(in io.Reader, out io.Writer, userAgent string, repoInfo *RepositoryInfo, authConfig *registrytypes.AuthConfig, actions ...string) (client.Repository, error) { server, err := Server(repoInfo.Index) if err != nil { return nil, err @@ -304,11 +304,18 @@ type ImageRefAndAuth struct { original string authConfig *registrytypes.AuthConfig reference reference.Named - repoInfo *registry.RepositoryInfo + repoInfo *RepositoryInfo tag string digest digest.Digest } +// RepositoryInfo describes a repository +type RepositoryInfo struct { + Name reference.Named + // Index points to registry information + Index *registrytypes.IndexInfo +} + // GetImageReferencesAndAuth retrieves the necessary reference and auth information for an image name // as an ImageRefAndAuth struct func GetImageReferencesAndAuth(ctx context.Context, @@ -321,15 +328,18 @@ func GetImageReferencesAndAuth(ctx context.Context, } // Resolve the Repository name from fqn to RepositoryInfo - repoInfo := registry.ParseRepositoryInfo(ref) - authConfig := authResolver(ctx, repoInfo.Index) + indexInfo := registry.NewIndexInfo(ref) + authConfig := authResolver(ctx, indexInfo) return ImageRefAndAuth{ original: imgName, authConfig: &authConfig, reference: ref, - repoInfo: repoInfo, - tag: getTag(ref), - digest: getDigest(ref), + repoInfo: &RepositoryInfo{ + Name: reference.TrimNamed(ref), + Index: indexInfo, + }, + tag: getTag(ref), + digest: getDigest(ref), }, nil } @@ -366,7 +376,7 @@ func (imgRefAuth *ImageRefAndAuth) Reference() reference.Named { } // RepoInfo returns the repository information for a given ImageRefAndAuth -func (imgRefAuth *ImageRefAndAuth) RepoInfo() *registry.RepositoryInfo { +func (imgRefAuth *ImageRefAndAuth) RepoInfo() *RepositoryInfo { return imgRefAuth.repoInfo } diff --git a/cli/trust/trust_push.go b/cli/trust/trust_push.go index 047990c4538e..87a33e7ca2b1 100644 --- a/cli/trust/trust_push.go +++ b/cli/trust/trust_push.go @@ -11,7 +11,6 @@ import ( "github.com/distribution/reference" "github.com/docker/cli/cli/streams" "github.com/docker/cli/internal/jsonstream" - "github.com/docker/cli/internal/registry" "github.com/docker/docker/api/types" registrytypes "github.com/docker/docker/api/types/registry" "github.com/opencontainers/go-digest" @@ -32,7 +31,7 @@ type Streams interface { // PushTrustedReference pushes a canonical reference to the trust server. // //nolint:gocyclo -func PushTrustedReference(ctx context.Context, ioStreams Streams, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader, userAgent string) error { +func PushTrustedReference(ctx context.Context, ioStreams Streams, repoInfo *RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader, userAgent string) error { // If it is a trusted push we would like to find the target entry which match the // tag provided in the function and then do an AddTarget later. notaryTarget := &client.Target{} diff --git a/internal/registry/config.go b/internal/registry/config.go index f2f026c5b9e5..04414cc88b36 100644 --- a/internal/registry/config.go +++ b/internal/registry/config.go @@ -252,27 +252,22 @@ func validateHostPort(s string) error { return nil } -// ParseRepositoryInfo performs the breakdown of a repository name into a -// [RepositoryInfo], but lacks registry configuration. -func ParseRepositoryInfo(reposName reference.Named) *RepositoryInfo { +// NewIndexInfo creates a new [registry.IndexInfo] or the given +// repository-name, and detects whether the registry is considered +// "secure" (non-localhost). +func NewIndexInfo(reposName reference.Named) *registry.IndexInfo { indexName := normalizeIndexName(reference.Domain(reposName)) if indexName == IndexName { - return &RepositoryInfo{ - Name: reference.TrimNamed(reposName), - Index: ®istry.IndexInfo{ - Name: IndexName, - Secure: true, - Official: true, - }, + return ®istry.IndexInfo{ + Name: IndexName, + Secure: true, + Official: true, } } - return &RepositoryInfo{ - Name: reference.TrimNamed(reposName), - Index: ®istry.IndexInfo{ - Name: indexName, - Secure: !isInsecure(indexName), - }, + return ®istry.IndexInfo{ + Name: indexName, + Secure: !isInsecure(indexName), } } diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go index e7cbef3a0b19..f1747aa86b62 100644 --- a/internal/registry/registry_test.go +++ b/internal/registry/registry_test.go @@ -9,7 +9,7 @@ import ( is "gotest.tools/v3/assert/cmp" ) -func TestParseRepositoryInfo(t *testing.T) { +func TestNewIndexInfo(t *testing.T) { type staticRepositoryInfo struct { Index *registry.IndexInfo RemoteName string @@ -269,12 +269,13 @@ func TestParseRepositoryInfo(t *testing.T) { named, err := reference.ParseNormalizedNamed(reposName) assert.NilError(t, err) - repoInfo := ParseRepositoryInfo(named) + indexInfo := NewIndexInfo(named) + repoInfoName := reference.TrimNamed(named) - assert.Check(t, is.DeepEqual(repoInfo.Index, expected.Index)) - assert.Check(t, is.Equal(reference.Path(repoInfo.Name), expected.RemoteName)) - assert.Check(t, is.Equal(reference.FamiliarName(repoInfo.Name), expected.LocalName)) - assert.Check(t, is.Equal(repoInfo.Name.Name(), expected.CanonicalName)) + assert.Check(t, is.DeepEqual(indexInfo, expected.Index)) + assert.Check(t, is.Equal(reference.Path(repoInfoName), expected.RemoteName)) + assert.Check(t, is.Equal(reference.FamiliarName(repoInfoName), expected.LocalName)) + assert.Check(t, is.Equal(repoInfoName.Name(), expected.CanonicalName)) }) } } diff --git a/internal/registry/types.go b/internal/registry/types.go deleted file mode 100644 index c35eb9fad2a8..000000000000 --- a/internal/registry/types.go +++ /dev/null @@ -1,13 +0,0 @@ -package registry - -import ( - "github.com/distribution/reference" - "github.com/docker/docker/api/types/registry" -) - -// RepositoryInfo describes a repository -type RepositoryInfo struct { - Name reference.Named - // Index points to registry information - Index *registry.IndexInfo -} From 6bfee62d6d4eca80a676965346ce51acbc392a5d Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 15 Aug 2025 18:56:38 +0200 Subject: [PATCH 027/193] opts: deprecate QuotedString The `QuotedString` option was added in [moby@e4c1f07] and [moby@abe32de] to work around a regression in Docker 1.13 that caused `docker-machine` to fail. `docker-machine` produced instructions on how to set up a cli to connect to the Machine it produced. These instructions used quotes around the paths for TLS certificates, but with an `=` for the flag's values instead of a space; due to this the shell would not handle stripping quotes, so the CLI would now get the value including quotes. Preserving quotes in such cases is expected (and standard behavior), but versions of Docker before 1.13 used a custom "mflag" package for flag parsing, and that package contained custom handling for quotes (added in [moby@0e9c40e]). For other flags, this problem could be solved by the user, but as these instructions were produced by `docker-machine`'s `config` command, an exception was made for the `--tls-xxx` flags. From [moby-29761]: > The flag trimming behaviour is really unusual, and I would say unexpected. > I think removing it is generally the right idea. Since we have one very > common case where it's necessary for backwards compatibility we need to > add a special case, but I don't think we should apply that case to every > flag. The `QuotedString` implementation has various limitations, as it doesn't follow the same handling of quotes as a shell would. Given that Docker Machine reached EOL a long time ago and other options, such as `docker context`, have been added to configure the CLI to connect to a specific host (with corresponding TLS configuration), we should remove the special handling for these flags, as it's inconsitent with all other flags, and not worth maintaining for a tool that no longer exists. This patch deprecates the `QuotedString` option and removes its use. A temporary, non-exported copy is added, but will be removed in the next release. [moby-29761]: https://github.com/moby/moby/issues/29761#issuecomment-270211265 [moby@e4c1f07]: https://github.com/moby/moby/commit/e4c1f0772923c3069ce14a82d445cd55af3382bc [moby@abe32de]: https://github.com/moby/moby/commit/abe32de6b46825300f612864e6b4c98606a5bb0e [moby@0e9c40e]: https://github.com/moby/moby/commit/0e9c40eb8243fa437bc6c3e93aaff64a10cb856e [moby@c79a169]: https://github.com/moby/moby/commit/c79a169a35f8ee0ecb66cfcffab4b0bd2c77f996 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 187a942a88913d31a1c9e511b920282222e75db0) Signed-off-by: Sebastiaan van Stijn --- cli/flags/options.go | 36 +++++++++++++++++++++++++++++++++--- opts/quotedstring.go | 4 ++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/cli/flags/options.go b/cli/flags/options.go index fc168984b44b..601c9c8eb7c6 100644 --- a/cli/flags/options.go +++ b/cli/flags/options.go @@ -90,9 +90,9 @@ func (o *ClientOptions) InstallFlags(flags *pflag.FlagSet) { KeyFile: filepath.Join(dockerCertPath, DefaultKeyFile), } tlsOptions := o.TLSOptions - flags.Var(opts.NewQuotedString(&tlsOptions.CAFile), "tlscacert", "Trust certs signed only by this CA") - flags.Var(opts.NewQuotedString(&tlsOptions.CertFile), "tlscert", "Path to TLS certificate file") - flags.Var(opts.NewQuotedString(&tlsOptions.KeyFile), "tlskey", "Path to TLS key file") + flags.Var("edString{&tlsOptions.CAFile}, "tlscacert", "Trust certs signed only by this CA") + flags.Var("edString{&tlsOptions.CertFile}, "tlscert", "Path to TLS certificate file") + flags.Var("edString{&tlsOptions.KeyFile}, "tlskey", "Path to TLS key file") // opts.ValidateHost is not used here, so as to allow connection helpers hostOpt := opts.NewNamedListOptsRef("hosts", &o.Hosts, nil) @@ -146,3 +146,33 @@ func SetLogLevel(logLevel string) { logrus.SetLevel(logrus.InfoLevel) } } + +type quotedString struct { + value *string +} + +func (s *quotedString) Set(val string) error { + *s.value = trimQuotes(val) + return nil +} + +func (*quotedString) Type() string { + return "string" +} + +func (s *quotedString) String() string { + return *s.value +} + +func trimQuotes(value string) string { + if len(value) < 2 { + return value + } + lastIndex := len(value) - 1 + for _, char := range []byte{'\'', '"'} { + if value[0] == char && value[lastIndex] == char { + return value[1:lastIndex] + } + } + return value +} diff --git a/opts/quotedstring.go b/opts/quotedstring.go index eb2ac7fbc8af..d1d8b09a1fec 100644 --- a/opts/quotedstring.go +++ b/opts/quotedstring.go @@ -2,6 +2,8 @@ package opts // QuotedString is a string that may have extra quotes around the value. The // quotes are stripped from the value. +// +// Deprecated: This option type is no longer used and will be removed in the next release. type QuotedString struct { value *string } @@ -35,6 +37,8 @@ func trimQuotes(value string) string { } // NewQuotedString returns a new quoted string option +// +// Deprecated: This option type is no longer used and will be removed in the next release. func NewQuotedString(value *string) *QuotedString { return &QuotedString{value: value} } From 713ed839fe117cdaaa5b114de58594f6fac8b2c3 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 7 Aug 2025 00:19:16 +0200 Subject: [PATCH 028/193] cli: remove HasCompletionArg utility It was only used in a single place and has no external consumers. Move it to where it's used to keep things together. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 5a9902255613ba63e81d97f7581bff9ecb55fe46) Signed-off-by: Sebastiaan van Stijn --- cli/cobra.go | 10 ---------- cmd/docker/docker.go | 12 +++++++++++- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cli/cobra.go b/cli/cobra.go index 7a14b6f483b9..b3e663423b7a 100644 --- a/cli/cobra.go +++ b/cli/cobra.go @@ -186,16 +186,6 @@ func DisableFlagsInUseLine(cmd *cobra.Command) { }) } -// HasCompletionArg returns true if a cobra completion arg request is found. -func HasCompletionArg(args []string) bool { - for _, arg := range args { - if arg == cobra.ShellCompRequestCmd || arg == cobra.ShellCompNoDescRequestCmd { - return true - } - } - return false -} - var helpCommand = &cobra.Command{ Use: "help [command]", Short: "Help about the command", diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index ccd61845a1d2..ad2cc096f23a 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -451,7 +451,7 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error { return err } - if cli.HasCompletionArg(args) { + if hasCompletionArg(args) { // We add plugin command stubs early only for completion. We don't // want to add them for normal command execution as it would cause // a significant performance hit. @@ -504,6 +504,16 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error { return err } +// hasCompletionArg returns true if a cobra completion arg request is found. +func hasCompletionArg(args []string) bool { + for _, arg := range args { + if arg == cobra.ShellCompRequestCmd || arg == cobra.ShellCompNoDescRequestCmd { + return true + } + } + return false +} + type versionDetails interface { CurrentVersion() string ServerInfo() command.ServerInfo From 60d16e20ac2479aada99a22da67cc8d4e9d49ec8 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 7 Aug 2025 17:42:47 +0200 Subject: [PATCH 029/193] cli: deprecate VisitAll, DisableFlagsInUseLine utilities These utilities were only used internally; create a local copy where used, and deprecate the ones in cli. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 6bd8a4b2b5d8854bf68256ee80964a9031ae931b) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/plugin/plugin.go | 15 +++++++++++- cli-plugins/plugin/plugin_test.go | 28 +++++++++++++++++++++++ cli/cobra.go | 14 ++++++++---- cli/cobra_test.go | 22 ------------------ cmd/docker/docker.go | 38 +++++++++++++++++++++++-------- cmd/docker/docker_test.go | 19 ++++++++++++++++ 6 files changed, 99 insertions(+), 37 deletions(-) create mode 100644 cli-plugins/plugin/plugin_test.go diff --git a/cli-plugins/plugin/plugin.go b/cli-plugins/plugin/plugin.go index 39d6d8694342..f4c80b3a48f3 100644 --- a/cli-plugins/plugin/plugin.go +++ b/cli-plugins/plugin/plugin.go @@ -175,11 +175,24 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta newMetadataSubcommand(plugin, meta), ) - cli.DisableFlagsInUseLine(cmd) + visitAll(cmd, + // prevent adding "[flags]" to the end of the usage line. + func(c *cobra.Command) { c.DisableFlagsInUseLine = true }, + ) return cli.NewTopLevelCommand(cmd, dockerCli, opts, cmd.Flags()) } +// visitAll traverses all commands from the root. +func visitAll(root *cobra.Command, fns ...func(*cobra.Command)) { + for _, cmd := range root.Commands() { + visitAll(cmd, fns...) + } + for _, fn := range fns { + fn(root) + } +} + func newMetadataSubcommand(plugin *cobra.Command, meta metadata.Metadata) *cobra.Command { if meta.ShortDescription == "" { meta.ShortDescription = plugin.Short diff --git a/cli-plugins/plugin/plugin_test.go b/cli-plugins/plugin/plugin_test.go new file mode 100644 index 000000000000..8b2b299fb1c0 --- /dev/null +++ b/cli-plugins/plugin/plugin_test.go @@ -0,0 +1,28 @@ +package plugin + +import ( + "slices" + "testing" + + "github.com/spf13/cobra" +) + +func TestVisitAll(t *testing.T) { + root := &cobra.Command{Use: "root"} + sub1 := &cobra.Command{Use: "sub1"} + sub1sub1 := &cobra.Command{Use: "sub1sub1"} + sub1sub2 := &cobra.Command{Use: "sub1sub2"} + sub2 := &cobra.Command{Use: "sub2"} + + root.AddCommand(sub1, sub2) + sub1.AddCommand(sub1sub1, sub1sub2) + + var visited []string + visitAll(root, func(ccmd *cobra.Command) { + visited = append(visited, ccmd.Name()) + }) + expected := []string{"sub1sub1", "sub1sub2", "sub1", "sub2", "root"} + if !slices.Equal(expected, visited) { + t.Errorf("expected %#v, got %#v", expected, visited) + } +} diff --git a/cli/cobra.go b/cli/cobra.go index b3e663423b7a..a75a66e66039 100644 --- a/cli/cobra.go +++ b/cli/cobra.go @@ -168,19 +168,25 @@ func (tcmd *TopLevelCommand) Initialize(ops ...command.CLIOption) error { } // VisitAll will traverse all commands from the root. -// This is different from the VisitAll of cobra.Command where only parents -// are checked. +// +// Deprecated: this utility was only used internally and will be removed in the next release. func VisitAll(root *cobra.Command, fn func(*cobra.Command)) { + visitAll(root, fn) +} + +func visitAll(root *cobra.Command, fn func(*cobra.Command)) { for _, cmd := range root.Commands() { - VisitAll(cmd, fn) + visitAll(cmd, fn) } fn(root) } // DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all // commands within the tree rooted at cmd. +// +// Deprecated: this utility was only used internally and will be removed in the next release. func DisableFlagsInUseLine(cmd *cobra.Command) { - VisitAll(cmd, func(ccmd *cobra.Command) { + visitAll(cmd, func(ccmd *cobra.Command) { // do not add a `[flags]` to the end of the usage line. ccmd.DisableFlagsInUseLine = true }) diff --git a/cli/cobra_test.go b/cli/cobra_test.go index e1d4bcb2d25f..9ca87ea5c836 100644 --- a/cli/cobra_test.go +++ b/cli/cobra_test.go @@ -10,28 +10,6 @@ import ( is "gotest.tools/v3/assert/cmp" ) -func TestVisitAll(t *testing.T) { - root := &cobra.Command{Use: "root"} - sub1 := &cobra.Command{Use: "sub1"} - sub1sub1 := &cobra.Command{Use: "sub1sub1"} - sub1sub2 := &cobra.Command{Use: "sub1sub2"} - sub2 := &cobra.Command{Use: "sub2"} - - root.AddCommand(sub1, sub2) - sub1.AddCommand(sub1sub1, sub1sub2) - - // Take the opportunity to test DisableFlagsInUseLine too - DisableFlagsInUseLine(root) - - var visited []string - VisitAll(root, func(ccmd *cobra.Command) { - visited = append(visited, ccmd.Name()) - assert.Assert(t, ccmd.DisableFlagsInUseLine, "DisableFlagsInUseLine not set on %q", ccmd.Name()) - }) - expected := []string{"sub1sub1", "sub1sub2", "sub1", "sub2", "root"} - assert.DeepEqual(t, expected, visited) -} - func TestVendorAndVersion(t *testing.T) { // Non plugin. assert.Equal(t, vendorAndVersion(&cobra.Command{Use: "test"}), "") diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index ad2cc096f23a..57c4b8ce26a6 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -163,8 +163,11 @@ func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand { cmd.SetOut(dockerCli.Out()) commands.AddCommands(cmd, dockerCli) - cli.DisableFlagsInUseLine(cmd) - setValidateArgs(dockerCli, cmd) + visitAll(cmd, + setValidateArgs(dockerCli), + // prevent adding "[flags]" to the end of the usage line. + func(c *cobra.Command) { c.DisableFlagsInUseLine = true }, + ) // flags must be the top-level command flags, not cmd.Flags() return cli.NewTopLevelCommand(cmd, dockerCli, opts, cmd.Flags()) @@ -265,14 +268,29 @@ func setHelpFunc(dockerCli command.Cli, cmd *cobra.Command) { }) } -func setValidateArgs(dockerCli command.Cli, cmd *cobra.Command) { - // The Args is handled by ValidateArgs in cobra, which does not allows a pre-hook. - // As a result, here we replace the existing Args validation func to a wrapper, - // where the wrapper will check to see if the feature is supported or not. - // The Args validation error will only be returned if the feature is supported. - cli.VisitAll(cmd, func(ccmd *cobra.Command) { +// visitAll traverses all commands from the root. +func visitAll(root *cobra.Command, fns ...func(*cobra.Command)) { + for _, cmd := range root.Commands() { + visitAll(cmd, fns...) + } + for _, fn := range fns { + fn(root) + } +} + +// The Args is handled by ValidateArgs in cobra, which does not allows a pre-hook. +// As a result, here we replace the existing Args validation func to a wrapper, +// where the wrapper will check to see if the feature is supported or not. +// The Args validation error will only be returned if the feature is supported. +func setValidateArgs(dockerCLI versionDetails) func(*cobra.Command) { + return func(ccmd *cobra.Command) { // if there is no tags for a command or any of its parent, // there is no need to wrap the Args validation. + // + // FIXME(thaJeztah): can we memoize properties of the parent? + // visitAll traverses root -> all childcommands, and hasTags + // goes the reverse (cmd -> visit all parents), so we may + // end traversing two directions. if !hasTags(ccmd) { return } @@ -283,12 +301,12 @@ func setValidateArgs(dockerCli command.Cli, cmd *cobra.Command) { cmdArgs := ccmd.Args ccmd.Args = func(cmd *cobra.Command, args []string) error { - if err := isSupported(cmd, dockerCli); err != nil { + if err := isSupported(cmd, dockerCLI); err != nil { return err } return cmdArgs(cmd, args) } - }) + } } func tryPluginRun(ctx context.Context, dockerCli command.Cli, cmd *cobra.Command, subcommand string, envs []string) error { diff --git a/cmd/docker/docker_test.go b/cmd/docker/docker_test.go index 48d37131c4a6..99e9583cfb0c 100644 --- a/cmd/docker/docker_test.go +++ b/cmd/docker/docker_test.go @@ -14,6 +14,7 @@ import ( "github.com/docker/cli/cli/debug" platformsignals "github.com/docker/cli/cmd/docker/internal/signals" "github.com/sirupsen/logrus" + "github.com/spf13/cobra" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) @@ -108,3 +109,21 @@ func TestUserTerminatedError(t *testing.T) { assert.Equal(t, getExitCode(context.Cause(notifyCtx)), 143) } + +func TestVisitAll(t *testing.T) { + root := &cobra.Command{Use: "root"} + sub1 := &cobra.Command{Use: "sub1"} + sub1sub1 := &cobra.Command{Use: "sub1sub1"} + sub1sub2 := &cobra.Command{Use: "sub1sub2"} + sub2 := &cobra.Command{Use: "sub2"} + + root.AddCommand(sub1, sub2) + sub1.AddCommand(sub1sub1, sub1sub2) + + var visited []string + visitAll(root, func(ccmd *cobra.Command) { + visited = append(visited, ccmd.Name()) + }) + expected := []string{"sub1sub1", "sub1sub2", "sub1", "sub2", "root"} + assert.DeepEqual(t, expected, visited) +} From ed52ada4a3c40ebe450ec8da9c6bbcd607fc4214 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 15 Aug 2025 14:33:42 +0200 Subject: [PATCH 030/193] cmd/docker: fix some minor linting issues Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 5a381189566471c9ee96c90ccb3b68009a7c3694) Signed-off-by: Sebastiaan van Stijn --- cmd/docker/docker.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index 57c4b8ce26a6..e56d33c89109 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -339,19 +339,19 @@ func tryPluginRun(ctx context.Context, dockerCli command.Cli, cmd *cobra.Command // signals to the subprocess because the shared // pgid makes the TTY a controlling terminal. // - // The plugin should have it's own copy of this + // The plugin should have its own copy of this // termination logic, and exit after 3 retries - // on it's own. + // on its own. if dockerCli.Out().IsTerminal() { return } - // Terminate the plugin server, which will - // close all connections with plugin - // subprocesses, and signal them to exit. + // Terminate the plugin server, which closes + // all connections with plugin subprocesses, + // and signal them to exit. // - // Repeated invocations will result in EINVAL, - // or EBADF; but that is fine for our purposes. + // Repeated invocations result in EINVAL or EBADF, + // but that is fine for our purposes. if srv != nil { _ = srv.Close() } @@ -369,15 +369,15 @@ func tryPluginRun(ctx context.Context, dockerCli command.Cli, cmd *cobra.Command go func() { retries := 0 - force := false // catch the first signal through context cancellation <-ctx.Done() - tryTerminatePlugin(force) + tryTerminatePlugin(false) // register subsequent signals signals := make(chan os.Signal, exitLimit) signal.Notify(signals, platformsignals.TerminationSignals...) + force := false for range signals { retries++ // If we're still running after 3 interruptions @@ -458,7 +458,7 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error { } }() } else { - fmt.Fprint(dockerCli.Err(), "Warning: Unexpected OTEL error, metrics may not be flushed") + _, _ = fmt.Fprint(dockerCli.Err(), "Warning: Unexpected OTEL error, metrics may not be flushed") } dockerCli.InstrumentCobraCommands(ctx, cmd) @@ -473,8 +473,7 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error { // We add plugin command stubs early only for completion. We don't // want to add them for normal command execution as it would cause // a significant performance hit. - err = pluginmanager.AddPluginCommandStubs(dockerCli, cmd) - if err != nil { + if err := pluginmanager.AddPluginCommandStubs(dockerCli, cmd); err != nil { return err } } From 5d6e64c4a6bcb6b1a0de773febfaecdeae6762fb Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 15 Aug 2025 12:54:14 +0200 Subject: [PATCH 031/193] docs: fix output example for docker system prune The example shows that the `--volumes` option is used, which in current versions of docker only removes "anonymous" volumes, but preserves named volume: $ docker system prune -a --volumes ... - all anonymous volumes not used by at least one container ... But the example output showed that a named volume ("named-vol") was deleted; Deleted Volumes: named-vol Co-authored-by: Roberto Villarreal Signed-off-by: Sebastiaan van Stijn (cherry picked from commit bf13010df894a7596584a1ceeb88e50754e692fa) Signed-off-by: Sebastiaan van Stijn --- docs/reference/commandline/system_prune.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/commandline/system_prune.md b/docs/reference/commandline/system_prune.md index 0d39d50feb99..39a58c54996c 100644 --- a/docs/reference/commandline/system_prune.md +++ b/docs/reference/commandline/system_prune.md @@ -72,7 +72,8 @@ my-network-a my-network-b Deleted Volumes: -named-vol +1e31bcd425e913d9f65ec0c3841e9c4ebb543aead2a1cfe0d95a7c5e88bb5026 +6a6ab3d6b8d740a1c1d4dbe36a9c5f043dd4bac5f78abfa7d1f2ae5789fe60b0 Deleted Images: untagged: my-curl:latest From 2b4e0a0f4577dc9bda5a28b33903c7303acc85d0 Mon Sep 17 00:00:00 2001 From: Austin Vazquez Date: Fri, 8 Aug 2025 08:34:44 -0500 Subject: [PATCH 032/193] update to go1.24.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - https://github.com/golang/go/issues?q=milestone%3AGo1.24.6+label%3ACherryPickApproved - full diff: golang/go@go1.24.5...go1.24.6 These minor releases include 2 security fixes following the security policy: - os/exec: LookPath may return unexpected paths If the PATH environment variable contains paths which are executables (rather than just directories), passing certain strings to LookPath ("", ".", and ".."), can result in the binaries listed in the PATH being unexpectedly returned. Thanks to Olivier Mengué for reporting this issue. This is CVE-2025-47906 and Go issue https://go.dev/issue/74466. - database/sql: incorrect results returned from Rows.Scan Cancelling a query (e.g. by cancelling the context passed to one of the query methods) during a call to the Scan method of the returned Rows can result in unexpected results if other queries are being made in parallel. This can result in a race condition that may overwrite the expected results with those of another query, causing the call to Scan to return either unexpected results from the other query or an error. We believe this affects most database/sql drivers. Thanks to Spike Curtis from Coder for reporting this issue. This is CVE-2025-47907 and https://go.dev/issue/74831. View the release notes for more information: https://go.dev/doc/devel/release#go1.24.6 Signed-off-by: Austin Vazquez (cherry picked from commit 6769f627467ffaed311eedb4422e441762baccd4) Signed-off-by: Sebastiaan van Stijn --- .github/workflows/codeql.yml | 2 +- .github/workflows/test.yml | 2 +- .golangci.yml | 2 +- Dockerfile | 2 +- docker-bake.hcl | 2 +- dockerfiles/Dockerfile.dev | 2 +- dockerfiles/Dockerfile.lint | 2 +- dockerfiles/Dockerfile.vendor | 2 +- e2e/testdata/Dockerfile.gencerts | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4ba63f3f030c..8c6e1d7b9d80 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -63,7 +63,7 @@ jobs: name: Update Go uses: actions/setup-go@v5 with: - go-version: "1.24.5" + go-version: "1.24.6" - name: Initialize CodeQL uses: github/codeql-action/init@v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7b84754248c7..854fe9e88cbc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,7 +66,7 @@ jobs: name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.24.5" + go-version: "1.24.6" - name: Test run: | diff --git a/.golangci.yml b/.golangci.yml index 0df356b2309d..5d3bfa892412 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,7 @@ run: # which causes it to fallback to go1.17 semantics. # # TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24 - go: "1.24.5" + go: "1.24.6" timeout: 5m diff --git a/Dockerfile b/Dockerfile index 6a697c1b543c..a29d9d5a7a1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ARG BASE_VARIANT=alpine ARG ALPINE_VERSION=3.21 ARG BASE_DEBIAN_DISTRO=bookworm -ARG GO_VERSION=1.24.5 +ARG GO_VERSION=1.24.6 ARG XX_VERSION=1.6.1 ARG GOVERSIONINFO_VERSION=v1.4.1 diff --git a/docker-bake.hcl b/docker-bake.hcl index 670e31c1f5dc..e486e2e037a9 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,5 +1,5 @@ variable "GO_VERSION" { - default = "1.24.5" + default = "1.24.6" } variable "VERSION" { default = "" diff --git a/dockerfiles/Dockerfile.dev b/dockerfiles/Dockerfile.dev index 385cead812ce..46107d78b27e 100644 --- a/dockerfiles/Dockerfile.dev +++ b/dockerfiles/Dockerfile.dev @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.5 +ARG GO_VERSION=1.24.6 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/dockerfiles/Dockerfile.lint b/dockerfiles/Dockerfile.lint index 345943c2e5c0..517badda71c1 100644 --- a/dockerfiles/Dockerfile.lint +++ b/dockerfiles/Dockerfile.lint @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.5 +ARG GO_VERSION=1.24.6 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/dockerfiles/Dockerfile.vendor b/dockerfiles/Dockerfile.vendor index e70a5921781d..aa329107ae51 100644 --- a/dockerfiles/Dockerfile.vendor +++ b/dockerfiles/Dockerfile.vendor @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.5 +ARG GO_VERSION=1.24.6 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/e2e/testdata/Dockerfile.gencerts b/e2e/testdata/Dockerfile.gencerts index 390ed58e0b47..fbb0c4b30941 100644 --- a/e2e/testdata/Dockerfile.gencerts +++ b/e2e/testdata/Dockerfile.gencerts @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.5 +ARG GO_VERSION=1.24.6 FROM golang:${GO_VERSION}-alpine AS generated ENV GOTOOLCHAIN=local From 511821052ae39cba0ad60f52e5b5b7ea330ec792 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 15 Aug 2025 22:16:11 +0200 Subject: [PATCH 033/193] opts: deprecate ValidateHost utility The `ValidateHost` option was introduced in [moby@1ba1138] to be used as validation func for the `--host` flag on the daemon in and CLI in [moby@5e3f6e7], but is no longer used since [cli@6f61cf0]. which added support for `ssh://` connections, and required validation elsewhere. [moby@1ba1138]: https://github.com/moby/moby/commit/1ba11384bf82f824b0efbab31aaca439cfba1b4f [moby@5e3f6e7]: https://github.com/moby/moby/commit/5e3f6e7023fbd0cfd1233a99c332801755340cfb [cli@6f61cf0]: https://github.com/docker/cli/commit/6f61cf053afc927379cfef91d241539c36d070f6 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit d0ac0acff01dc8834e7829679c3183e59d9af247) Signed-off-by: Sebastiaan van Stijn --- opts/hosts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/hosts.go b/opts/hosts.go index 552ab6b4a552..87e1a1da755b 100644 --- a/opts/hosts.go +++ b/opts/hosts.go @@ -34,7 +34,7 @@ const ( // ValidateHost validates that the specified string is a valid host and returns it. // -// TODO(thaJeztah): ValidateHost appears to be unused; deprecate it. +// Deprecated: this function is no longer used, and will be removed in the next release. func ValidateHost(val string) (string, error) { host := strings.TrimSpace(val) // The empty string means default and is not handled by parseDockerDaemonHost From 034dc932d76a3d5357f8cd14995518983ea1b072 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 28 Jul 2025 14:55:43 +0200 Subject: [PATCH 034/193] remove aliases for containerd/errdefs, disallow docker/errdefs We transitioned most functionality of docker/errdefs to containerd errdefs module, and the docker/errdefs package should no longer be used. Because of that, there will no longer be ambiguity, so we can remove the aliases for this package, and use it as "errdefs". Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 89d8c8a2a74323f82344107c3ff93536a2b13ccc) Signed-off-by: Sebastiaan van Stijn --- .golangci.yml | 9 +++++---- cli/command/container/create.go | 4 ++-- cli/command/container/errors.go | 6 +++--- cli/command/container/rm.go | 4 ++-- cli/command/context/create.go | 4 ++-- cli/command/context/remove_test.go | 4 ++-- cli/command/context/use_test.go | 4 ++-- cli/command/defaultcontextstore_test.go | 8 ++++---- cli/command/image/remove.go | 4 ++-- cli/command/network/remove.go | 4 ++-- cli/command/registry/login.go | 4 ++-- cli/command/service/inspect.go | 6 +++--- cli/command/service/logs.go | 6 +++--- cli/command/stack/swarm/deploy_composefile.go | 8 ++++---- cli/command/system/inspect.go | 4 ++-- cli/context/store/errors.go | 6 +++--- cli/context/store/metadata_test.go | 6 +++--- cli/context/store/store_test.go | 6 +++--- cli/context/store/tlsstore_test.go | 6 +++--- cmd/docker/docker.go | 4 ++-- internal/registry/config_test.go | 4 ++-- internal/registry/service.go | 4 ++-- 22 files changed, 58 insertions(+), 57 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 5d3bfa892412..bf093a63c7ac 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -86,6 +86,8 @@ linters: desc: Use github.com/moby/sys/userns instead. - pkg: "github.com/containerd/containerd/platforms" desc: The containerd platforms package was migrated to a separate module. Use github.com/containerd/platforms instead. + - pkg: "github.com/docker/docker/errdefs" + desc: Use github.com/containerd/errdefs instead. - pkg: "github.com/docker/docker/pkg/system" desc: This package should not be used unless strictly necessary. - pkg: "github.com/docker/distribution/uuid" @@ -124,10 +126,9 @@ linters: no-unaliased: true alias: - # Enforce alias to prevent it accidentally being used instead of our - # own errdefs package (or vice-versa). - - pkg: github.com/containerd/errdefs - alias: cerrdefs + # Should no longer be aliased, because we no longer allow moby/docker errdefs. + - pkg: "github.com/docker/docker/errdefs" + alias: "" - pkg: github.com/opencontainers/image-spec/specs-go/v1 alias: ocispec # Enforce that gotest.tools/v3/assert/cmp is always aliased as "is" diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 88d274dfbf3b..9906f63b227e 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -11,7 +11,7 @@ import ( "path" "strings" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/containerd/platforms" "github.com/distribution/reference" "github.com/docker/cli/cli" @@ -341,7 +341,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name) if err != nil { // Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior. - if cerrdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing { + if errdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing { if !options.quiet { // we don't want to write to stdout anything apart from container.ID _, _ = fmt.Fprintf(dockerCli.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef)) diff --git a/cli/command/container/errors.go b/cli/command/container/errors.go index 957aa25fa6c5..d0d6f7055329 100644 --- a/cli/command/container/errors.go +++ b/cli/command/container/errors.go @@ -1,9 +1,9 @@ package container -import cerrdefs "github.com/containerd/errdefs" +import "github.com/containerd/errdefs" func invalidParameter(err error) error { - if err == nil || cerrdefs.IsInvalidArgument(err) { + if err == nil || errdefs.IsInvalidArgument(err) { return err } return invalidParameterErr{err} @@ -17,7 +17,7 @@ func (e invalidParameterErr) Unwrap() error { } func notFound(err error) error { - if err == nil || cerrdefs.IsNotFound(err) { + if err == nil || errdefs.IsNotFound(err) { return err } return notFoundErr{err} diff --git a/cli/command/container/rm.go b/cli/command/container/rm.go index 3206bb59924c..f2b360657d01 100644 --- a/cli/command/container/rm.go +++ b/cli/command/container/rm.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" @@ -75,7 +75,7 @@ func runRm(ctx context.Context, dockerCLI command.Cli, opts *rmOptions) error { var errs []error for _, name := range opts.containers { if err := <-errChan; err != nil { - if opts.force && cerrdefs.IsNotFound(err) { + if opts.force && errdefs.IsNotFound(err) { _, _ = fmt.Fprintln(dockerCLI.Err(), err) continue } diff --git a/cli/command/context/create.go b/cli/command/context/create.go index 313aca142e4a..308aa8720069 100644 --- a/cli/command/context/create.go +++ b/cli/command/context/create.go @@ -8,7 +8,7 @@ import ( "errors" "fmt" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" @@ -122,7 +122,7 @@ func checkContextNameForCreation(s store.Reader, name string) error { if err := store.ValidateContextName(name); err != nil { return err } - if _, err := s.GetMetadata(name); !cerrdefs.IsNotFound(err) { + if _, err := s.GetMetadata(name); !errdefs.IsNotFound(err) { if err != nil { return fmt.Errorf("error while getting existing contexts: %w", err) } diff --git a/cli/command/context/remove_test.go b/cli/command/context/remove_test.go index bbcddec6d16b..9ebb944251d6 100644 --- a/cli/command/context/remove_test.go +++ b/cli/command/context/remove_test.go @@ -4,7 +4,7 @@ import ( "path/filepath" "testing" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" "gotest.tools/v3/assert" @@ -18,7 +18,7 @@ func TestRemove(t *testing.T) { _, err := cli.ContextStore().GetMetadata("current") assert.NilError(t, err) _, err = cli.ContextStore().GetMetadata("other") - assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) + assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) } func TestRemoveNotAContext(t *testing.T) { diff --git a/cli/command/context/use_test.go b/cli/command/context/use_test.go index 8c7265dac829..ebab2d5e7f5a 100644 --- a/cli/command/context/use_test.go +++ b/cli/command/context/use_test.go @@ -9,7 +9,7 @@ import ( "runtime" "testing" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" @@ -47,7 +47,7 @@ func TestUse(t *testing.T) { func TestUseNoExist(t *testing.T) { cli := makeFakeCli(t) err := newUseCommand(cli).RunE(nil, []string{"test"}) - assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) + assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) } // TestUseDefaultWithoutConfigFile verifies that the CLI does not create diff --git a/cli/command/defaultcontextstore_test.go b/cli/command/defaultcontextstore_test.go index d0c8d09b758a..4facfc985337 100644 --- a/cli/command/defaultcontextstore_test.go +++ b/cli/command/defaultcontextstore_test.go @@ -7,7 +7,7 @@ import ( "crypto/rand" "testing" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/context/docker" "github.com/docker/cli/cli/context/store" @@ -158,7 +158,7 @@ func TestErrCreateDefault(t *testing.T) { Metadata: testContext{Bar: "baz"}, Name: "default", }) - assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) + assert.Check(t, is.ErrorType(err, errdefs.IsInvalidArgument)) assert.Error(t, err, "default context cannot be created nor updated") } @@ -166,7 +166,7 @@ func TestErrRemoveDefault(t *testing.T) { meta := testDefaultMetadata() s := testStore(t, meta, store.ContextTLSData{}) err := s.Remove("default") - assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) + assert.Check(t, is.ErrorType(err, errdefs.IsInvalidArgument)) assert.Error(t, err, "default context cannot be removed") } @@ -174,5 +174,5 @@ func TestErrTLSDataError(t *testing.T) { meta := testDefaultMetadata() s := testStore(t, meta, store.ContextTLSData{}) _, err := s.GetTLSData("default", "noop", "noop") - assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) + assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) } diff --git a/cli/command/image/remove.go b/cli/command/image/remove.go index 7c8b49ee0a99..977a297ae7d4 100644 --- a/cli/command/image/remove.go +++ b/cli/command/image/remove.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/containerd/platforms" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" @@ -79,7 +79,7 @@ func runRemove(ctx context.Context, dockerCLI command.Cli, opts removeOptions, i for _, img := range images { dels, err := apiClient.ImageRemove(ctx, img, options) if err != nil { - if !cerrdefs.IsNotFound(err) { + if !errdefs.IsNotFound(err) { fatalErr = true } errs = append(errs, err) diff --git a/cli/command/network/remove.go b/cli/command/network/remove.go index 69578a48c0ab..7446260318e4 100644 --- a/cli/command/network/remove.go +++ b/cli/command/network/remove.go @@ -5,7 +5,7 @@ import ( "fmt" "strconv" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" @@ -59,7 +59,7 @@ func runRemove(ctx context.Context, dockerCLI command.Cli, networks []string, op } } if err := apiClient.NetworkRemove(ctx, name); err != nil { - if opts.force && cerrdefs.IsNotFound(err) { + if opts.force && errdefs.IsNotFound(err) { continue } _, _ = fmt.Fprintln(dockerCLI.Err(), err) diff --git a/cli/command/registry/login.go b/cli/command/registry/login.go index a216908505c3..9ce9ce1cd459 100644 --- a/cli/command/registry/login.go +++ b/cli/command/registry/login.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" @@ -159,7 +159,7 @@ func loginWithStoredCredentials(ctx context.Context, dockerCLI command.Cli, auth response, err := dockerCLI.Client().RegistryLogin(ctx, authConfig) if err != nil { - if cerrdefs.IsUnauthorized(err) { + if errdefs.IsUnauthorized(err) { _, _ = fmt.Fprintln(dockerCLI.Err(), "Stored credentials invalid or expired") } else { _, _ = fmt.Fprintln(dockerCLI.Err(), "Login did not succeed, error:", err) diff --git a/cli/command/service/inspect.go b/cli/command/service/inspect.go index e3ae937125bc..01edff6ac2e5 100644 --- a/cli/command/service/inspect.go +++ b/cli/command/service/inspect.go @@ -7,7 +7,7 @@ import ( "context" "strings" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" @@ -67,7 +67,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) getRef := func(ref string) (any, []byte, error) { // Service inspect shows defaults values in empty fields. service, _, err := client.ServiceInspectWithRaw(ctx, ref, swarm.ServiceInspectOptions{InsertDefaults: true}) - if err == nil || !cerrdefs.IsNotFound(err) { + if err == nil || !errdefs.IsNotFound(err) { return service, nil, err } return nil, nil, errors.Errorf("Error: no such service: %s", ref) @@ -75,7 +75,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) getNetwork := func(ref string) (any, []byte, error) { nw, _, err := client.NetworkInspectWithRaw(ctx, ref, network.InspectOptions{Scope: "swarm"}) - if err == nil || !cerrdefs.IsNotFound(err) { + if err == nil || !errdefs.IsNotFound(err) { return nw, nil, err } return nil, nil, errors.Errorf("Error: no such network: %s", ref) diff --git a/cli/command/service/logs.go b/cli/command/service/logs.go index 7c651d859bb2..a3e29f6b454c 100644 --- a/cli/command/service/logs.go +++ b/cli/command/service/logs.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" @@ -93,12 +93,12 @@ func runLogs(ctx context.Context, dockerCli command.Cli, opts *logsOptions) erro service, _, err := apiClient.ServiceInspectWithRaw(ctx, opts.target, swarm.ServiceInspectOptions{}) if err != nil { // if it's any error other than service not found, it's Real - if !cerrdefs.IsNotFound(err) { + if !errdefs.IsNotFound(err) { return err } task, _, err := apiClient.TaskInspectWithRaw(ctx, opts.target) if err != nil { - if cerrdefs.IsNotFound(err) { + if errdefs.IsNotFound(err) { // if the task isn't found, rewrite the error to be clear // that we looked for services AND tasks and found none err = fmt.Errorf("no such task or service: %v", opts.target) diff --git a/cli/command/stack/swarm/deploy_composefile.go b/cli/command/stack/swarm/deploy_composefile.go index edde88962a9d..8c0bad15b660 100644 --- a/cli/command/stack/swarm/deploy_composefile.go +++ b/cli/command/stack/swarm/deploy_composefile.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/docker/cli/cli/command" servicecli "github.com/docker/cli/cli/command/service" "github.com/docker/cli/cli/command/stack/options" @@ -97,7 +97,7 @@ func validateExternalNetworks(ctx context.Context, apiClient client.NetworkAPICl } nw, err := apiClient.NetworkInspect(ctx, networkName, network.InspectOptions{}) switch { - case cerrdefs.IsNotFound(err): + case errdefs.IsNotFound(err): return fmt.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed", networkName) case err != nil: return err @@ -119,7 +119,7 @@ func createSecrets(ctx context.Context, dockerCLI command.Cli, secrets []swarm.S if err := apiClient.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil { return fmt.Errorf("failed to update secret %s: %w", secretSpec.Name, err) } - case cerrdefs.IsNotFound(err): + case errdefs.IsNotFound(err): // secret does not exist, then we create a new one. _, _ = fmt.Fprintln(dockerCLI.Out(), "Creating secret", secretSpec.Name) if _, err := apiClient.SecretCreate(ctx, secretSpec); err != nil { @@ -143,7 +143,7 @@ func createConfigs(ctx context.Context, dockerCLI command.Cli, configs []swarm.C if err := apiClient.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil { return fmt.Errorf("failed to update config %s: %w", configSpec.Name, err) } - case cerrdefs.IsNotFound(err): + case errdefs.IsNotFound(err): // config does not exist, then we create a new one. _, _ = fmt.Fprintln(dockerCLI.Out(), "Creating config", configSpec.Name) if _, err := apiClient.ConfigCreate(ctx, configSpec); err != nil { diff --git a/cli/command/system/inspect.go b/cli/command/system/inspect.go index 0afe65e41108..1765538e2fa4 100644 --- a/cli/command/system/inspect.go +++ b/cli/command/system/inspect.go @@ -9,7 +9,7 @@ import ( "fmt" "strings" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" @@ -279,7 +279,7 @@ func inspectAll(ctx context.Context, dockerCLI command.Cli, getSize bool, typeCo } func isErrSkippable(err error) bool { - return cerrdefs.IsNotFound(err) || + return errdefs.IsNotFound(err) || strings.Contains(err.Error(), "not supported") || strings.Contains(err.Error(), "invalid reference format") } diff --git a/cli/context/store/errors.go b/cli/context/store/errors.go index e85ce325a9a7..cabc3f7a342b 100644 --- a/cli/context/store/errors.go +++ b/cli/context/store/errors.go @@ -1,9 +1,9 @@ package store -import cerrdefs "github.com/containerd/errdefs" +import "github.com/containerd/errdefs" func invalidParameter(err error) error { - if err == nil || cerrdefs.IsInvalidArgument(err) { + if err == nil || errdefs.IsInvalidArgument(err) { return err } return invalidParameterErr{err} @@ -14,7 +14,7 @@ type invalidParameterErr struct{ error } func (invalidParameterErr) InvalidParameter() {} func notFound(err error) error { - if err == nil || cerrdefs.IsNotFound(err) { + if err == nil || errdefs.IsNotFound(err) { return err } return notFoundErr{err} diff --git a/cli/context/store/metadata_test.go b/cli/context/store/metadata_test.go index beaf133d379f..e7a6318e9c13 100644 --- a/cli/context/store/metadata_test.go +++ b/cli/context/store/metadata_test.go @@ -8,7 +8,7 @@ import ( "path/filepath" "testing" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) @@ -26,7 +26,7 @@ func testMetadata(name string) Metadata { func TestMetadataGetNotExisting(t *testing.T) { testee := metadataStore{root: t.TempDir(), config: testCfg} _, err := testee.get("noexist") - assert.ErrorType(t, err, cerrdefs.IsNotFound) + assert.ErrorType(t, err, errdefs.IsNotFound) } func TestMetadataCreateGetRemove(t *testing.T) { @@ -60,7 +60,7 @@ func TestMetadataCreateGetRemove(t *testing.T) { assert.NilError(t, testee.remove("test-context")) assert.NilError(t, testee.remove("test-context")) // support duplicate remove _, err = testee.get("test-context") - assert.ErrorType(t, err, cerrdefs.IsNotFound) + assert.ErrorType(t, err, errdefs.IsNotFound) } func TestMetadataRespectJsonAnnotation(t *testing.T) { diff --git a/cli/context/store/store_test.go b/cli/context/store/store_test.go index 2d3074783a01..a5354538d083 100644 --- a/cli/context/store/store_test.go +++ b/cli/context/store/store_test.go @@ -17,7 +17,7 @@ import ( "path/filepath" "testing" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) @@ -107,7 +107,7 @@ func TestRemove(t *testing.T) { })) assert.NilError(t, s.Remove("source")) _, err = s.GetMetadata("source") - assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) + assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) f, err := s.ListTLSFiles("source") assert.NilError(t, err) assert.Equal(t, 0, len(f)) @@ -122,7 +122,7 @@ func TestListEmptyStore(t *testing.T) { func TestErrHasCorrectContext(t *testing.T) { _, err := New(t.TempDir(), testCfg).GetMetadata("no-exists") assert.ErrorContains(t, err, "no-exists") - assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) + assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) } func TestDetectImportContentType(t *testing.T) { diff --git a/cli/context/store/tlsstore_test.go b/cli/context/store/tlsstore_test.go index a6aae68ae0c3..799362d54e14 100644 --- a/cli/context/store/tlsstore_test.go +++ b/cli/context/store/tlsstore_test.go @@ -3,7 +3,7 @@ package store import ( "testing" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "gotest.tools/v3/assert" ) @@ -13,7 +13,7 @@ func TestTlsCreateUpdateGetRemove(t *testing.T) { const contextName = "test-ctx" _, err := testee.getData(contextName, "test-ep", "test-data") - assert.ErrorType(t, err, cerrdefs.IsNotFound) + assert.ErrorType(t, err, errdefs.IsNotFound) err = testee.createOrUpdate(contextName, "test-ep", "test-data", []byte("data")) assert.NilError(t, err) @@ -29,7 +29,7 @@ func TestTlsCreateUpdateGetRemove(t *testing.T) { err = testee.removeEndpoint(contextName, "test-ep") assert.NilError(t, err) _, err = testee.getData(contextName, "test-ep", "test-data") - assert.ErrorType(t, err, cerrdefs.IsNotFound) + assert.ErrorType(t, err, errdefs.IsNotFound) } func TestTlsListAndBatchRemove(t *testing.T) { diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index ccd61845a1d2..32091106314b 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -10,7 +10,7 @@ import ( "strings" "syscall" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/docker/cli/cli" pluginmanager "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/socket" @@ -41,7 +41,7 @@ func main() { os.Exit(getExitCode(err)) } - if err != nil && !cerrdefs.IsCanceled(err) { + if err != nil && !errdefs.IsCanceled(err) { if err.Error() != "" { _, _ = fmt.Fprintln(os.Stderr, err) } diff --git a/internal/registry/config_test.go b/internal/registry/config_test.go index a5574a298d45..9c21929f281b 100644 --- a/internal/registry/config_test.go +++ b/internal/registry/config_test.go @@ -3,7 +3,7 @@ package registry import ( "testing" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "gotest.tools/v3/assert" ) @@ -86,7 +86,7 @@ func TestLoadInsecureRegistries(t *testing.T) { t.Fatalf("expect error '%s', got no error", testCase.err) } assert.ErrorContains(t, err, testCase.err) - assert.Check(t, cerrdefs.IsInvalidArgument(err)) + assert.Check(t, errdefs.IsInvalidArgument(err)) } } } diff --git a/internal/registry/service.go b/internal/registry/service.go index 1936a2972a1f..a270b5855371 100644 --- a/internal/registry/service.go +++ b/internal/registry/service.go @@ -8,7 +8,7 @@ import ( "net/url" "strings" - cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs" "github.com/containerd/log" "github.com/docker/docker/api/types/registry" ) @@ -60,7 +60,7 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use for _, endpoint := range endpoints { authToken, err := loginV2(ctx, authConfig, endpoint, userAgent) if err != nil { - if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || cerrdefs.IsUnauthorized(err) { + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || errdefs.IsUnauthorized(err) { // Failed to authenticate; don't continue with (non-TLS) endpoints. return "", err } From 79dd3a3c794ec5eafe7794107c41487197a40991 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 4 Aug 2025 09:14:13 +0200 Subject: [PATCH 035/193] cli-plugins/manager: un-export "Candidate" interface It is for internal use for mocking purposes, and is not part of any public interface / signature. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 54367b32837e30dc3c4fe08584552812e959500d) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/candidate.go | 6 ------ cli-plugins/manager/plugin.go | 8 +++++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cli-plugins/manager/candidate.go b/cli-plugins/manager/candidate.go index d809926cf62b..a4253968dcca 100644 --- a/cli-plugins/manager/candidate.go +++ b/cli-plugins/manager/candidate.go @@ -6,12 +6,6 @@ import ( "github.com/docker/cli/cli-plugins/metadata" ) -// Candidate represents a possible plugin candidate, for mocking purposes -type Candidate interface { - Path() string - Metadata() ([]byte, error) -} - type candidate struct { path string } diff --git a/cli-plugins/manager/plugin.go b/cli-plugins/manager/plugin.go index fa846452b548..ed4dac54e6a4 100644 --- a/cli-plugins/manager/plugin.go +++ b/cli-plugins/manager/plugin.go @@ -31,12 +31,18 @@ type Plugin struct { ShadowedPaths []string `json:",omitempty"` } +// pluginCandidate represents a possible plugin candidate, for mocking purposes. +type pluginCandidate interface { + Path() string + Metadata() ([]byte, error) +} + // newPlugin determines if the given candidate is valid and returns a // Plugin. If the candidate fails one of the tests then `Plugin.Err` // is set, and is always a `pluginError`, but the `Plugin` is still // returned with no error. An error is only returned due to a // non-recoverable error. -func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) { +func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) { path := c.Path() if path == "" { return Plugin{}, errors.New("plugin candidate path cannot be empty") From 09efe3f408342133aee8b95335059a8ad4dc55a4 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 4 Aug 2025 10:45:02 +0200 Subject: [PATCH 036/193] cli-plugins/manager: fix Plugin marshaling with regular errors Go does not by default marshal `error` type fields to JSON. The manager package therefore implemented a `pluginError` type that implements [encoding.TextMarshaler]. However, the field was marked as a regular `error`, which made it brittle; assining any other type of error would result in the error being discarded in the marshaled JSON (as used in `docker info` output), resulting in the error being marshaled as `{}`. This patch adds a custom `MarshalJSON()` on the `Plugin` type itself so that any error is rendered. It checks if the error used already implements [encoding.TextMarshaler], otherwise wraps the error in a `pluginError`. [encoding.TextMarshaler]: https://pkg.go.dev/encoding#TextMarshaler Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 549d39a89fe3e6546c55af2a00c9ea0b77305ef3) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/plugin.go | 17 ++++++++++++ cli-plugins/manager/plugin_test.go | 43 ++++++++++++++++++++++++++++++ cli/command/system/info_test.go | 3 ++- 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 cli-plugins/manager/plugin_test.go diff --git a/cli-plugins/manager/plugin.go b/cli-plugins/manager/plugin.go index ed4dac54e6a4..e72d9039b895 100644 --- a/cli-plugins/manager/plugin.go +++ b/cli-plugins/manager/plugin.go @@ -2,6 +2,7 @@ package manager import ( "context" + "encoding" "encoding/json" "errors" "fmt" @@ -31,6 +32,22 @@ type Plugin struct { ShadowedPaths []string `json:",omitempty"` } +// MarshalJSON implements [json.Marshaler] to handle marshaling the +// [Plugin.Err] field (Go doesn't marshal errors by default). +func (p *Plugin) MarshalJSON() ([]byte, error) { + type Alias Plugin // avoid recursion + + cp := *p // shallow copy to avoid mutating original + + if cp.Err != nil { + if _, ok := cp.Err.(encoding.TextMarshaler); !ok { + cp.Err = &pluginError{cp.Err} + } + } + + return json.Marshal((*Alias)(&cp)) +} + // pluginCandidate represents a possible plugin candidate, for mocking purposes. type pluginCandidate interface { Path() string diff --git a/cli-plugins/manager/plugin_test.go b/cli-plugins/manager/plugin_test.go new file mode 100644 index 000000000000..84d71d5187d6 --- /dev/null +++ b/cli-plugins/manager/plugin_test.go @@ -0,0 +1,43 @@ +package manager + +import ( + "encoding/json" + "errors" + "testing" + + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" +) + +func TestPluginMarshal(t *testing.T) { + const jsonWithError = `{"Name":"some-plugin","Err":"something went wrong"}` + const jsonNoError = `{"Name":"some-plugin"}` + + tests := []struct { + doc string + error error + expected string + }{ + { + doc: "no error", + expected: jsonNoError, + }, + { + doc: "regular error", + error: errors.New("something went wrong"), + expected: jsonWithError, + }, + { + doc: "custom error", + error: NewPluginError("something went wrong"), + expected: jsonWithError, + }, + } + for _, tc := range tests { + t.Run(tc.doc, func(t *testing.T) { + actual, err := json.Marshal(&Plugin{Name: "some-plugin", Err: tc.error}) + assert.NilError(t, err) + assert.Check(t, is.Equal(string(actual), tc.expected)) + }) + } +} diff --git a/cli/command/system/info_test.go b/cli/command/system/info_test.go index 726d1ea5a2d4..6b193b7fe921 100644 --- a/cli/command/system/info_test.go +++ b/cli/command/system/info_test.go @@ -2,6 +2,7 @@ package system import ( "encoding/base64" + "errors" "net" "testing" "time" @@ -220,7 +221,7 @@ var samplePluginsInfo = []pluginmanager.Plugin{ { Name: "badplugin", Path: "/path/to/docker-badplugin", - Err: pluginmanager.NewPluginError("something wrong"), + Err: errors.New("something wrong"), }, } From 5e29918a44eba0940baef98150e78965bbd58b62 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 4 Aug 2025 11:01:24 +0200 Subject: [PATCH 037/193] cli-plugins/manager: un-export "NewPluginError" It is for internal use, and no longer needed for testing, now that the `Plugin` type handles marshalling errors. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 1cc698c68fdacb3cc8ec9933dde4360a0e108de4) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/error.go | 4 ++-- cli-plugins/manager/error_test.go | 2 +- cli-plugins/manager/plugin.go | 10 +++++----- cli-plugins/manager/plugin_test.go | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cli-plugins/manager/error.go b/cli-plugins/manager/error.go index 1b5f4f60e54e..04bf3decb480 100644 --- a/cli-plugins/manager/error.go +++ b/cli-plugins/manager/error.go @@ -47,8 +47,8 @@ func wrapAsPluginError(err error, msg string) error { return &pluginError{cause: fmt.Errorf("%s: %w", msg, err)} } -// NewPluginError creates a new pluginError, analogous to +// newPluginError creates a new pluginError, analogous to // errors.Errorf. -func NewPluginError(msg string, args ...any) error { +func newPluginError(msg string, args ...any) error { return &pluginError{cause: fmt.Errorf(msg, args...)} } diff --git a/cli-plugins/manager/error_test.go b/cli-plugins/manager/error_test.go index c4cb19bd55d5..682860daadb0 100644 --- a/cli-plugins/manager/error_test.go +++ b/cli-plugins/manager/error_test.go @@ -10,7 +10,7 @@ import ( ) func TestPluginError(t *testing.T) { - err := NewPluginError("new error") + err := newPluginError("new error") assert.Check(t, is.Error(err, "new error")) inner := errors.New("testing") diff --git a/cli-plugins/manager/plugin.go b/cli-plugins/manager/plugin.go index e72d9039b895..2caae39b0292 100644 --- a/cli-plugins/manager/plugin.go +++ b/cli-plugins/manager/plugin.go @@ -86,7 +86,7 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) { // Now apply the candidate tests, so these update p.Err. if !pluginNameRe.MatchString(p.Name) { - p.Err = NewPluginError("plugin candidate %q did not match %q", p.Name, pluginNameRe.String()) + p.Err = newPluginError("plugin candidate %q did not match %q", p.Name, pluginNameRe.String()) return p, nil } @@ -98,11 +98,11 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) { continue } if cmd.Name() == p.Name { - p.Err = NewPluginError("plugin %q duplicates builtin command", p.Name) + p.Err = newPluginError("plugin %q duplicates builtin command", p.Name) return p, nil } if cmd.HasAlias(p.Name) { - p.Err = NewPluginError("plugin %q duplicates an alias of builtin command %q", p.Name, cmd.Name()) + p.Err = newPluginError("plugin %q duplicates an alias of builtin command %q", p.Name, cmd.Name()) return p, nil } } @@ -119,11 +119,11 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) { return p, nil } if p.Metadata.SchemaVersion != "0.1.0" { - p.Err = NewPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion) + p.Err = newPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion) return p, nil } if p.Metadata.Vendor == "" { - p.Err = NewPluginError("plugin metadata does not define a vendor") + p.Err = newPluginError("plugin metadata does not define a vendor") return p, nil } return p, nil diff --git a/cli-plugins/manager/plugin_test.go b/cli-plugins/manager/plugin_test.go index 84d71d5187d6..ace51c12c630 100644 --- a/cli-plugins/manager/plugin_test.go +++ b/cli-plugins/manager/plugin_test.go @@ -29,7 +29,7 @@ func TestPluginMarshal(t *testing.T) { }, { doc: "custom error", - error: NewPluginError("something went wrong"), + error: newPluginError("something went wrong"), expected: jsonWithError, }, } From fe1d790d8edf6dd347ee993bed57fad41566f673 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 4 Aug 2025 11:06:49 +0200 Subject: [PATCH 038/193] cli-plugins/manager: deprecate "IsNotFound" These errors satisfy errdefs.IsNotFound, so make it a wrapper, and deprecate it. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 71460215d3f41ae449bbd5e32618fc33b638159e) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/manager.go | 19 ++++++++----------- cli-plugins/manager/manager_test.go | 7 ++++--- cmd/docker/builder.go | 3 ++- cmd/docker/docker.go | 6 +++--- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/cli-plugins/manager/manager.go b/cli-plugins/manager/manager.go index 25515bccb868..52be4da40b5c 100644 --- a/cli-plugins/manager/manager.go +++ b/cli-plugins/manager/manager.go @@ -9,6 +9,7 @@ import ( "strings" "sync" + "github.com/containerd/errdefs" "github.com/docker/cli/cli-plugins/metadata" "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" @@ -40,15 +41,11 @@ func (e errPluginNotFound) Error() string { return "Error: No such CLI plugin: " + string(e) } -type notFound interface{ NotFound() } - // IsNotFound is true if the given error is due to a plugin not being found. +// +// Deprecated: use [errdefs.IsNotFound]. func IsNotFound(err error) bool { - if e, ok := err.(*pluginError); ok { - err = e.Cause() - } - _, ok := err.(notFound) - return ok + return errdefs.IsNotFound(err) } // getPluginDirs returns the platform-specific locations to search for plugins @@ -127,7 +124,7 @@ func getPlugin(name string, pluginDirs []string, rootcmd *cobra.Command) (*Plugi if err != nil { return nil, err } - if !IsNotFound(p.Err) { + if !errdefs.IsNotFound(p.Err) { p.ShadowedPaths = paths[1:] } return &p, nil @@ -164,7 +161,7 @@ func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, e if err != nil { return err } - if !IsNotFound(p.Err) { + if !errdefs.IsNotFound(p.Err) { p.ShadowedPaths = paths[1:] mu.Lock() defer mu.Unlock() @@ -185,9 +182,9 @@ func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, e return plugins, nil } -// PluginRunCommand returns an "os/exec".Cmd which when .Run() will execute the named plugin. +// PluginRunCommand returns an [os/exec.Cmd] which when [os/exec.Cmd.Run] will execute the named plugin. // The rootcmd argument is referenced to determine the set of builtin commands in order to detect conficts. -// The error returned satisfies the IsNotFound() predicate if no plugin was found or if the first candidate plugin was invalid somehow. +// The error returned satisfies the [errdefs.IsNotFound] predicate if no plugin was found or if the first candidate plugin was invalid somehow. func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Command) (*exec.Cmd, error) { // This uses the full original args, not the args which may // have been provided by cobra to our caller. This is because diff --git a/cli-plugins/manager/manager_test.go b/cli-plugins/manager/manager_test.go index 64609fbbad39..d1223ed4ec86 100644 --- a/cli-plugins/manager/manager_test.go +++ b/cli-plugins/manager/manager_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/containerd/errdefs" "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/internal/test" @@ -131,7 +132,7 @@ echo '{"SchemaVersion":"0.1.0"}'`, fs.WithMode(0o777)), _, err = GetPlugin("ccc", cli, &cobra.Command{}) assert.Error(t, err, "Error: No such CLI plugin: ccc") - assert.Assert(t, IsNotFound(err)) + assert.Assert(t, errdefs.IsNotFound(err)) } func TestListPluginsIsSorted(t *testing.T) { @@ -166,8 +167,8 @@ func TestErrPluginNotFound(t *testing.T) { var err error = errPluginNotFound("test") err.(errPluginNotFound).NotFound() assert.Error(t, err, "Error: No such CLI plugin: test") - assert.Assert(t, IsNotFound(err)) - assert.Assert(t, !IsNotFound(nil)) + assert.Assert(t, errdefs.IsNotFound(err)) + assert.Assert(t, !errdefs.IsNotFound(nil)) } func TestGetPluginDirs(t *testing.T) { diff --git a/cmd/docker/builder.go b/cmd/docker/builder.go index cccae304fe5e..00fc1b40f1ab 100644 --- a/cmd/docker/builder.go +++ b/cmd/docker/builder.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/containerd/errdefs" pluginmanager "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/metadata" "github.com/docker/cli/cli/command" @@ -36,7 +37,7 @@ const ( ) func newBuilderError(errorMsg string, pluginLoadErr error) error { - if pluginmanager.IsNotFound(pluginLoadErr) { + if errdefs.IsNotFound(pluginLoadErr) { return errors.New(errorMsg) } if pluginLoadErr != nil { diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index 32091106314b..b8e57add4afd 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -201,7 +201,7 @@ func setupHelpCommand(dockerCli command.Cli, rootCmd, helpCmd *cobra.Command) { if err == nil { return helpcmd.Run() } - if !pluginmanager.IsNotFound(err) { + if !errdefs.IsNotFound(err) { return fmt.Errorf("unknown help topic: %v", strings.Join(args, " ")) } } @@ -240,7 +240,7 @@ func setHelpFunc(dockerCli command.Cli, cmd *cobra.Command) { if err == nil { return } - if !pluginmanager.IsNotFound(err) { + if !errdefs.IsNotFound(err) { ccmd.Println(err) return } @@ -473,7 +473,7 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error { } return nil } - if !pluginmanager.IsNotFound(err) { + if !errdefs.IsNotFound(err) { // For plugin not found we fall through to // cmd.Execute() which deals with reporting // "command not found" in a consistent way. From 7f699066bd047c2aee5236f625597d1a9cc53c44 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 4 Aug 2025 11:11:52 +0200 Subject: [PATCH 039/193] cli-plugins/manager: pluginError: remove Causer interface We no longer depend on this interface and it implements Unwrap for native handling by go stdlib. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit d789bac04af184bd11496f5ef6d34fe7e64949cf) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/error.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cli-plugins/manager/error.go b/cli-plugins/manager/error.go index 04bf3decb480..709149151161 100644 --- a/cli-plugins/manager/error.go +++ b/cli-plugins/manager/error.go @@ -23,11 +23,6 @@ func (e *pluginError) Error() string { return e.cause.Error() } -// Cause satisfies the errors.causer interface for pluginError. -func (e *pluginError) Cause() error { - return e.cause -} - // Unwrap provides compatibility for Go 1.13 error chains. func (e *pluginError) Unwrap() error { return e.cause From ac88b74462ba6c8f720e6e6d182f93bdb74cc2f2 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 4 Aug 2025 11:15:35 +0200 Subject: [PATCH 040/193] cli-plugins/manager: wrapAsPluginError: don't special-case nil This was a pattern inheritted from pkg/errors.Wrapf, which ignored nil errors for convenience. However, it is error-prone, as it is not obvious when returning a nil-error. All call-sites using `wrapAsPluginError` already do a check for nil errors, so remove this code to prevent hard to find bugs. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 50963accec79a87652014d6b40a1d1cee4054f51) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/error.go | 3 --- cli-plugins/manager/error_test.go | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli-plugins/manager/error.go b/cli-plugins/manager/error.go index 709149151161..aaedae14fad3 100644 --- a/cli-plugins/manager/error.go +++ b/cli-plugins/manager/error.go @@ -36,9 +36,6 @@ func (e *pluginError) MarshalText() (text []byte, err error) { // wrapAsPluginError wraps an error in a pluginError with an // additional message. func wrapAsPluginError(err error, msg string) error { - if err == nil { - return nil - } return &pluginError{cause: fmt.Errorf("%s: %w", msg, err)} } diff --git a/cli-plugins/manager/error_test.go b/cli-plugins/manager/error_test.go index 682860daadb0..2e92001c20ed 100644 --- a/cli-plugins/manager/error_test.go +++ b/cli-plugins/manager/error_test.go @@ -21,4 +21,7 @@ func TestPluginError(t *testing.T) { actual, err := json.Marshal(err) assert.Check(t, err) assert.Check(t, is.Equal(`"wrapping: testing"`, string(actual))) + + err = wrapAsPluginError(nil, "wrapping") + assert.Check(t, is.Error(err, "wrapping: %!w()")) } From 099820d8cafd8b1db627683552dcb3762c92e609 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 4 Aug 2025 11:25:01 +0200 Subject: [PATCH 041/193] cli-plugins/manager: deprecate metadata aliases These aliases were added in 4321293972a4ed804e8c063868cc5da6147ce73b (part of v28.0), but did not deprecate them. They are no longer used in the CLI itself, but may be used by cli-plugin implementations. This deprecates the aliases in `cli-plugins/manager` in favor of their equivalent in `cli-plugins/manager/metadata`: - `NamePrefix` - `MetadataSubcommandName` - `HookSubcommandName` - `Metadata` - `ReexecEnvvar` Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 5876b2941c8b0cbefade16d6f857f1899a43f51b) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/metadata.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cli-plugins/manager/metadata.go b/cli-plugins/manager/metadata.go index 9bddb121422d..6fda7fa1af05 100644 --- a/cli-plugins/manager/metadata.go +++ b/cli-plugins/manager/metadata.go @@ -6,18 +6,26 @@ import ( const ( // NamePrefix is the prefix required on all plugin binary names + // + // Deprecated: use [metadata.NamePrefix]. This alias will be removed in a future release. NamePrefix = metadata.NamePrefix // MetadataSubcommandName is the name of the plugin subcommand // which must be supported by every plugin and returns the // plugin metadata. + // + // Deprecated: use [metadata.MetadataSubcommandName]. This alias will be removed in a future release. MetadataSubcommandName = metadata.MetadataSubcommandName // HookSubcommandName is the name of the plugin subcommand // which must be implemented by plugins declaring support // for hooks in their metadata. + // + // Deprecated: use [metadata.HookSubcommandName]. This alias will be removed in a future release. HookSubcommandName = metadata.HookSubcommandName ) // Metadata provided by the plugin. +// +// Deprecated: use [metadata.Metadata]. This alias will be removed in a future release. type Metadata = metadata.Metadata From 79e0b1d7d9f1fd1d54e037c2c69765feb7f917e4 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 4 Aug 2025 11:27:07 +0200 Subject: [PATCH 042/193] cli-plugins/manager: remove deprecated ResourceAttributesEnvvar This const was deprecated in 9dc175d6ef7057c0e86d3a097b86012676fe4a95, which is part of v28.0, so let's remove it. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 513ceeec0a4cb73722a4f2ea6e06ffb4ee6b46fb) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/manager.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cli-plugins/manager/manager.go b/cli-plugins/manager/manager.go index 52be4da40b5c..b76dbbe37667 100644 --- a/cli-plugins/manager/manager.go +++ b/cli-plugins/manager/manager.go @@ -24,12 +24,6 @@ const ( // plugin. Assuming $PATH and $CWD remain unchanged this should allow // the plugin to re-execute the original CLI. ReexecEnvvar = metadata.ReexecEnvvar - - // ResourceAttributesEnvvar is the name of the envvar that includes additional - // resource attributes for OTEL. - // - // Deprecated: The "OTEL_RESOURCE_ATTRIBUTES" env-var is part of the OpenTelemetry specification; users should define their own const for this. This const will be removed in the next release. - ResourceAttributesEnvvar = "OTEL_RESOURCE_ATTRIBUTES" ) // errPluginNotFound is the error returned when a plugin could not be found. From ff42ff9f066b81ddf7c5f3e81116a4f69d5346b2 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 15 Aug 2025 23:39:39 +0200 Subject: [PATCH 043/193] cli/flags: use a regular StringArray for the `--host` / `-H` flag The ClientOptions struct and related flags were inherited from the Moby repository, where originally the CLI and Daemon used the same implementation and had a "Common" options struct. When the CLI moved to a separate repository, those structs were duplicated, but some daemon-specific logic remained. For example, the daemon can be configured to listen on multiple ports and sockets ([moby@dede158]), but the CLI [can only connect to a single host][1]. The daemon config also had to account for flags conflicting with `daemon.json`, and use special flag-vars for this ([moby@677a6b3]). Unfortunately, the `ClientConfig` struct became part of the public API and is used as argument in various places, but we can remove the use of the special flag var. This patch replaces the use of `NewNamedListOptsRef` for a regular `StringArray`. Unfortunately this changes the flag's type description from `list` to `stringArray`, but we can look at changing that separately. [moby@dede158]: https://github.com/moby/moby/commit/dede1585ee00f957e153691c464aab293c2dc469 [1]: https://github.com/moby/moby/blob/0af135e9065562e14a77439e13a29b4f1eb627a0/docker/docker.go#L191-L193 [moby@677a6b3]: https://github.com/moby/moby/commit/677a6b3506107468ed8c00331991afd9176fa0b9 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 5ee2906e784da604d0df340f9ba41a921a01f2f1) Signed-off-by: Sebastiaan van Stijn --- cli/flags/options.go | 7 +++---- docs/reference/commandline/docker.md | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/cli/flags/options.go b/cli/flags/options.go index 601c9c8eb7c6..f24992f5bc9d 100644 --- a/cli/flags/options.go +++ b/cli/flags/options.go @@ -6,7 +6,6 @@ import ( "path/filepath" "github.com/docker/cli/cli/config" - "github.com/docker/cli/opts" "github.com/docker/docker/client" "github.com/docker/go-connections/tlsconfig" "github.com/sirupsen/logrus" @@ -94,9 +93,9 @@ func (o *ClientOptions) InstallFlags(flags *pflag.FlagSet) { flags.Var("edString{&tlsOptions.CertFile}, "tlscert", "Path to TLS certificate file") flags.Var("edString{&tlsOptions.KeyFile}, "tlskey", "Path to TLS key file") - // opts.ValidateHost is not used here, so as to allow connection helpers - hostOpt := opts.NewNamedListOptsRef("hosts", &o.Hosts, nil) - flags.VarP(hostOpt, "host", "H", "Daemon socket to connect to") + // TODO(thaJeztah): show the default host. + // TODO(thaJeztah): this should be a string, not an "array" as we only allow a single host. + flags.StringArrayVarP(&o.Hosts, "host", "H", nil, "Daemon socket to connect to") flags.StringVarP(&o.Context, "context", "c", "", `Name of the context to use to connect to the daemon (overrides `+client.EnvOverrideHost+` env var and default context set with "docker context use")`) } diff --git a/docs/reference/commandline/docker.md b/docs/reference/commandline/docker.md index 69b1c91303be..00ab5cce62ab 100644 --- a/docs/reference/commandline/docker.md +++ b/docs/reference/commandline/docker.md @@ -69,18 +69,18 @@ The base command for the Docker CLI. ### Options -| Name | Type | Default | Description | -|:---------------------------------|:---------|:-------------------------|:--------------------------------------------------------------------------------------------------------------------------------------| -| `--config` | `string` | `/root/.docker` | Location of client config files | -| `-c`, `--context` | `string` | | Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with `docker context use`) | -| `-D`, `--debug` | `bool` | | Enable debug mode | -| [`-H`](#host), [`--host`](#host) | `list` | | Daemon socket to connect to | -| `-l`, `--log-level` | `string` | `info` | Set the logging level (`debug`, `info`, `warn`, `error`, `fatal`) | -| `--tls` | `bool` | | Use TLS; implied by --tlsverify | -| `--tlscacert` | `string` | `/root/.docker/ca.pem` | Trust certs signed only by this CA | -| `--tlscert` | `string` | `/root/.docker/cert.pem` | Path to TLS certificate file | -| `--tlskey` | `string` | `/root/.docker/key.pem` | Path to TLS key file | -| `--tlsverify` | `bool` | | Use TLS and verify the remote | +| Name | Type | Default | Description | +|:---------------------------------|:--------------|:-------------------------|:--------------------------------------------------------------------------------------------------------------------------------------| +| `--config` | `string` | `/root/.docker` | Location of client config files | +| `-c`, `--context` | `string` | | Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with `docker context use`) | +| `-D`, `--debug` | `bool` | | Enable debug mode | +| [`-H`](#host), [`--host`](#host) | `stringArray` | | Daemon socket to connect to | +| `-l`, `--log-level` | `string` | `info` | Set the logging level (`debug`, `info`, `warn`, `error`, `fatal`) | +| `--tls` | `bool` | | Use TLS; implied by --tlsverify | +| `--tlscacert` | `string` | `/root/.docker/ca.pem` | Trust certs signed only by this CA | +| `--tlscert` | `string` | `/root/.docker/cert.pem` | Path to TLS certificate file | +| `--tlskey` | `string` | `/root/.docker/key.pem` | Path to TLS key file | +| `--tlsverify` | `bool` | | Use TLS and verify the remote | From 7091e8bea4172cefb27e863c484d98205d34cedc Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sat, 16 Aug 2025 19:10:58 +0200 Subject: [PATCH 044/193] cli/flags: add "hostVar" to handle --host / -H as a single string hostVar is used for the '--host' / '-H' flag to set [ClientOptions.Hosts]. The [ClientOptions.Hosts] field is a slice because it was originally shared with the daemon config. However, the CLI only allows for a single host to be specified. hostVar presents itself as a "string", but stores the value in a string slice. It produces an error when trying to set multiple values, matching the check in [getServerHost]. [getServerHost]: https://github.com/docker/cli/blob/7eab668982645def1cd46fe1b60894cba6fd17a4/cli/command/cli.go#L542-L551 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit f14eeeb361acbdf8c30cd3c57c927b5a7a724c99) Signed-off-by: Sebastiaan van Stijn --- cli/flags/options.go | 36 +++++++++++++++++++++++++++- docs/reference/commandline/docker.md | 24 +++++++++---------- e2e/cli-plugins/flags_test.go | 5 ++-- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/cli/flags/options.go b/cli/flags/options.go index f24992f5bc9d..309cb4616fa3 100644 --- a/cli/flags/options.go +++ b/cli/flags/options.go @@ -1,6 +1,7 @@ package flags import ( + "errors" "fmt" "os" "path/filepath" @@ -53,6 +54,39 @@ var ( dockerTLS = os.Getenv(EnvEnableTLS) != "" ) +// hostVar is used for the '--host' / '-H' flag to set [ClientOptions.Hosts]. +// The [ClientOptions.Hosts] field is a slice because it was originally shared +// with the daemon config. However, the CLI only allows for a single host to +// be specified. +// +// hostVar presents itself as a "string", but stores the value in a string +// slice. It produces an error when trying to set multiple values, matching +// the check in [getServerHost]. +// +// [getServerHost]: https://github.com/docker/cli/blob/7eab668982645def1cd46fe1b60894cba6fd17a4/cli/command/cli.go#L542-L551 +type hostVar struct { + dst *[]string + set bool +} + +func (h *hostVar) String() string { + if h.dst == nil || len(*h.dst) == 0 { + return "" + } + return (*h.dst)[0] +} + +func (h *hostVar) Set(s string) error { + if h.set { + return errors.New("specify only one -H") + } + *h.dst = []string{s} + h.set = true + return nil +} + +func (*hostVar) Type() string { return "string" } + // ClientOptions are the options used to configure the client cli. type ClientOptions struct { Debug bool @@ -95,7 +129,7 @@ func (o *ClientOptions) InstallFlags(flags *pflag.FlagSet) { // TODO(thaJeztah): show the default host. // TODO(thaJeztah): this should be a string, not an "array" as we only allow a single host. - flags.StringArrayVarP(&o.Hosts, "host", "H", nil, "Daemon socket to connect to") + flags.VarP(&hostVar{dst: &o.Hosts}, "host", "H", "Daemon socket to connect to") flags.StringVarP(&o.Context, "context", "c", "", `Name of the context to use to connect to the daemon (overrides `+client.EnvOverrideHost+` env var and default context set with "docker context use")`) } diff --git a/docs/reference/commandline/docker.md b/docs/reference/commandline/docker.md index 00ab5cce62ab..03dbddc8cced 100644 --- a/docs/reference/commandline/docker.md +++ b/docs/reference/commandline/docker.md @@ -69,18 +69,18 @@ The base command for the Docker CLI. ### Options -| Name | Type | Default | Description | -|:---------------------------------|:--------------|:-------------------------|:--------------------------------------------------------------------------------------------------------------------------------------| -| `--config` | `string` | `/root/.docker` | Location of client config files | -| `-c`, `--context` | `string` | | Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with `docker context use`) | -| `-D`, `--debug` | `bool` | | Enable debug mode | -| [`-H`](#host), [`--host`](#host) | `stringArray` | | Daemon socket to connect to | -| `-l`, `--log-level` | `string` | `info` | Set the logging level (`debug`, `info`, `warn`, `error`, `fatal`) | -| `--tls` | `bool` | | Use TLS; implied by --tlsverify | -| `--tlscacert` | `string` | `/root/.docker/ca.pem` | Trust certs signed only by this CA | -| `--tlscert` | `string` | `/root/.docker/cert.pem` | Path to TLS certificate file | -| `--tlskey` | `string` | `/root/.docker/key.pem` | Path to TLS key file | -| `--tlsverify` | `bool` | | Use TLS and verify the remote | +| Name | Type | Default | Description | +|:---------------------------------|:---------|:-------------------------|:--------------------------------------------------------------------------------------------------------------------------------------| +| `--config` | `string` | `/root/.docker` | Location of client config files | +| `-c`, `--context` | `string` | | Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with `docker context use`) | +| `-D`, `--debug` | `bool` | | Enable debug mode | +| [`-H`](#host), [`--host`](#host) | `string` | | Daemon socket to connect to | +| `-l`, `--log-level` | `string` | `info` | Set the logging level (`debug`, `info`, `warn`, `error`, `fatal`) | +| `--tls` | `bool` | | Use TLS; implied by --tlsverify | +| `--tlscacert` | `string` | `/root/.docker/ca.pem` | Trust certs signed only by this CA | +| `--tlscert` | `string` | `/root/.docker/cert.pem` | Path to TLS certificate file | +| `--tlskey` | `string` | `/root/.docker/key.pem` | Path to TLS key file | +| `--tlsverify` | `bool` | | Use TLS and verify the remote | diff --git a/e2e/cli-plugins/flags_test.go b/e2e/cli-plugins/flags_test.go index 69fe6d6ad3b4..0cb24ba2488e 100644 --- a/e2e/cli-plugins/flags_test.go +++ b/e2e/cli-plugins/flags_test.go @@ -1,6 +1,7 @@ package cliplugins import ( + "fmt" "os" "testing" @@ -91,9 +92,9 @@ func TestGlobalArgsOnlyParsedOnce(t *testing.T) { // This is checking the precondition wrt -H mentioned in the function comment name: "fails-if-H-used-twice", args: []string{"-H", dh, "-H", dh, "version", "-f", "{{.Client.Version}}"}, - expectedExitCode: 1, + expectedExitCode: 125, expectedOut: icmd.None, - expectedErr: "Specify only one -H", + expectedErr: fmt.Sprintf(`invalid argument %q for "-H, --host" flag: specify only one -H`, dh), }, { name: "builtin", From d1617cb0c034efbbc2bd58fc56cee3ad5cb0b906 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 18 Aug 2025 17:59:16 +0200 Subject: [PATCH 045/193] Deprecate special handling for quoted values for TLS flags The `--tlscacert`, `--tlscert`, and `--tlskey` command-line flags had non-standard behavior for handling values contained in quotes (`"` or `'`). Normally, quotes are handled by the shell, for example, in the following example, the shell takes care of handling quotes before passing the values to the `docker` CLI: docker --some-option "some-value-in-quotes" ... However, when passing values using an equal sign (`=`), this may not happen and values may be handled including quotes; docker --some-option="some-value-in-quotes" ... This caused issues with "Docker Machine", which used this format as part of its `docker-machine config` output, and the CLI carried special, non-standard handling for these flags. Docker Machine reached EOL, and this special handling made the processing of flag values inconsistent with other flags used, so this behavior is deprecated. Users depending on this behavior are recommended to specify the quoted values using a space between the flag and its value, as illustrated above. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit ee05a7151319775dea762e98c56d196c4a215215) Signed-off-by: Sebastiaan van Stijn --- docs/deprecated.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/deprecated.md b/docs/deprecated.md index 1a3c353da69f..74aab4643e64 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -53,6 +53,7 @@ The following table provides an overview of the current status of deprecated fea | Status | Feature | Deprecated | Remove | |------------|------------------------------------------------------------------------------------------------------------------------------------|------------|--------| +| Deprecated | [Special handling for quoted values for TLS flags](#special-handling-for-quoted-values-for-tls-flags) | v28.4 | v29.0 | | Deprecated | [Empty/nil fields in image Config from inspect API](#emptynil-fields-in-image-config-from-inspect-api) | v28.3 | v29.0 | | Deprecated | [Configuration for pushing non-distributable artifacts](#configuration-for-pushing-non-distributable-artifacts) | v28.0 | v29.0 | | Deprecated | [`--time` option on `docker stop` and `docker restart`](#--time-option-on-docker-stop-and-docker-restart) | v28.0 | - | @@ -121,6 +122,39 @@ The following table provides an overview of the current status of deprecated fea | Removed | [`--run` flag on `docker commit`](#--run-flag-on-docker-commit) | v0.10 | v1.13 | | Removed | [Three arguments form in `docker import`](#three-arguments-form-in-docker-import) | v0.6.7 | v1.12 | + +### Special handling for quoted values for TLS flags + +**Deprecated in Release: v28.4** +**Target For Removal In Release: v29.0** + +The `--tlscacert`, `--tlscert`, and `--tlskey` command-line flags had +non-standard behavior for handling values contained in quotes (`"` or `'`). +Normally, quotes are handled by the shell, for example, in the following +example, the shell takes care of handling quotes before passing the values +to the `docker` CLI: + +```console +docker --some-option "some-value-in-quotes" ... +``` + +However, when passing values using an equal sign (`=`), this may not happen +and values may be handled including quotes; + +```console +docker --some-option="some-value-in-quotes" ... +``` + +This caused issues with "Docker Machine", which used this format as part +of its `docker-machine config` output, and the CLI carried special, non-standard +handling for these flags. + +Docker Machine reached EOL, and this special handling made the processing +of flag values inconsistent with other flags used, so this behavior is +deprecated. Users depending on this behavior are recommended to specify +the quoted values using a space between the flag and its value, as illustrated +above. + ### Empty/nil fields in image Config from inspect API **Deprecated in Release: v28.3** From 7ccd6d29c6a46be15ce36e93ab187382fe5defbb Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 18 Aug 2025 17:04:10 +0200 Subject: [PATCH 046/193] opts: deprecate NewNamedListOptsRef, NewNamedMapOpts The `NewNamedListOptsRef`, `NewNamedMapOpts` and related `NamedListOpts`, `NamedMapOpts`, and `NamedOption` interface were added in [moby@677a6b3], which added support for a `daemon.json` configuration file. That change required a way to correlate command-line flags with their corresponding fields in the `daemon.json` to detect conflicting options. At the time, the CLI and daemon were produced from the same code, and shared packages for command-line options, but when the CLI was moved to a separate repository, these options were inherited. [moby@677a6b3]: https://github.com/moby/moby/commit/677a6b3506107468ed8c00331991afd9176fa0b9 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 6f0c66c152e2e64ad38e75a560449a292cc28f7a) Signed-off-by: Sebastiaan van Stijn --- opts/opts.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index 1a885db30eff..94eda0560a17 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -134,6 +134,8 @@ func (opts *ListOpts) WithValidator(validator ValidatorFctType) *ListOpts { // NamedOption is an interface that list and map options // with names implement. +// +// Deprecated: NamedOption is no longer used and will be removed in the next release. type NamedOption interface { Name() string } @@ -141,6 +143,8 @@ type NamedOption interface { // NamedListOpts is a ListOpts with a configuration name. // This struct is useful to keep reference to the assigned // field name in the internal configuration struct. +// +// Deprecated: NamedListOpts is no longer used and will be removed in the next release. type NamedListOpts struct { name string ListOpts @@ -149,6 +153,8 @@ type NamedListOpts struct { var _ NamedOption = &NamedListOpts{} // NewNamedListOptsRef creates a reference to a new NamedListOpts struct. +// +// Deprecated: NewNamedListOptsRef is no longer used and will be removed in the next release. func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts { return &NamedListOpts{ name: name, @@ -157,6 +163,8 @@ func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctTy } // Name returns the name of the NamedListOpts in the configuration. +// +// Deprecated: NamedListOpts is no longer used and will be removed in the next release. func (o *NamedListOpts) Name() string { return o.name } @@ -210,6 +218,8 @@ func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { // NamedMapOpts is a MapOpts struct with a configuration name. // This struct is useful to keep reference to the assigned // field name in the internal configuration struct. +// +// Deprecated: NamedMapOpts is no longer used and will be removed in the next release. type NamedMapOpts struct { name string MapOpts @@ -218,6 +228,8 @@ type NamedMapOpts struct { var _ NamedOption = &NamedMapOpts{} // NewNamedMapOpts creates a reference to a new NamedMapOpts struct. +// +// Deprecated: NamedMapOpts is no longer used and will be removed in the next release. func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts { return &NamedMapOpts{ name: name, @@ -226,6 +238,8 @@ func NewNamedMapOpts(name string, values map[string]string, validator ValidatorF } // Name returns the name of the NamedMapOpts in the configuration. +// +// Deprecated: NamedMapOpts is no longer used and will be removed in the next release. func (o *NamedMapOpts) Name() string { return o.name } From 0e474e38f138c25645fdef497c8c546a3a94239f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 18 Aug 2025 15:21:14 +0200 Subject: [PATCH 047/193] [28.x] cli/command/container: TestRunPullTermination: rewrite with streamformatter This makes the test slightly closer to the actual code in the daemon producing the progress response; https://github.com/moby/moby/blob/cd844fd0b2047eff6854600d375545b3ec01de48/daemon/images/image_pull.go#L58-L70 https://github.com/moby/moby/blob/cd844fd0b2047eff6854600d375545b3ec01de48/daemon/internal/distribution/utils/progress.go#L14-L34 This is a modified version of 69854c4e08c502f57a1fed4ec58236fae229db37 with some changes specific to the 28.x branch (the variant on master had some patches for the moby/api and moby/client transition). Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 69854c4e08c502f57a1fed4ec58236fae229db37) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/run_test.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/cli/command/container/run_test.go b/cli/command/container/run_test.go index f60ebde360a1..17a931c8c00a 100644 --- a/cli/command/container/run_test.go +++ b/cli/command/container/run_test.go @@ -2,9 +2,7 @@ package container import ( "context" - "encoding/json" "errors" - "fmt" "io" "net" "syscall" @@ -20,7 +18,8 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/network" - "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/docker/pkg/progress" + "github.com/docker/docker/pkg/streamformatter" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/pflag" "gotest.tools/v3/assert" @@ -242,23 +241,19 @@ func TestRunPullTermination(t *testing.T) { _ = server.Close() }) go func() { - enc := json.NewEncoder(server) + id := test.RandomID()[:12] // short-ID + progressOutput := streamformatter.NewJSONProgressOutput(server, true) for i := 0; i < 100; i++ { select { case <-ctx.Done(): assert.NilError(t, server.Close(), "failed to close imageCreateFunc server") return default: - assert.NilError(t, enc.Encode(jsonmessage.JSONMessage{ - Status: "Downloading", - ID: fmt.Sprintf("id-%d", i), - TimeNano: time.Now().UnixNano(), - Time: time.Now().Unix(), - Progress: &jsonmessage.JSONProgress{ - Current: int64(i), - Total: 100, - Start: 0, - }, + assert.NilError(t, progressOutput.WriteProgress(progress.Progress{ + ID: id, + Message: "Downloading", + Current: int64(i), + Total: 100, })) time.Sleep(100 * time.Millisecond) } From 98b82d515651d61301fd78abeb227bddc58696a2 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 18 Aug 2025 21:50:53 +0200 Subject: [PATCH 048/193] cli-plugins/manager: deprecate annotation metadata aliases These aliases were added in 292713c887b17339106e9f7f3647ed50bebc675d (part of v28.0), but did not deprecate them. They are no longer used in the CLI itself, but may be used by cli-plugin implementations. This deprecates the aliases in `cli-plugins/manager` in favor of their equivalent in `cli-plugins/manager/metadata`: - `CommandAnnotationPlugin` - `CommandAnnotationPluginVendor` - `CommandAnnotationPluginVersion` - `CommandAnnotationPluginInvalid` - `CommandAnnotationPluginCommandPath` Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 72f76f2720e5a31918f991ee797bfc3041fad37c) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/annotations.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cli-plugins/manager/annotations.go b/cli-plugins/manager/annotations.go index 8fc76b73c9bc..abe47fe091a5 100644 --- a/cli-plugins/manager/annotations.go +++ b/cli-plugins/manager/annotations.go @@ -6,25 +6,35 @@ const ( // CommandAnnotationPlugin is added to every stub command added by // AddPluginCommandStubs with the value "true" and so can be // used to distinguish plugin stubs from regular commands. + // + // Deprecated: use [metadata.CommandAnnotationPlugin]. This alias will be removed in the next release. CommandAnnotationPlugin = metadata.CommandAnnotationPlugin // CommandAnnotationPluginVendor is added to every stub command // added by AddPluginCommandStubs and contains the vendor of // that plugin. + // + // Deprecated: use [metadata.CommandAnnotationPluginVendor]. This alias will be removed in the next release. CommandAnnotationPluginVendor = metadata.CommandAnnotationPluginVendor // CommandAnnotationPluginVersion is added to every stub command // added by AddPluginCommandStubs and contains the version of // that plugin. + // + // Deprecated: use [metadata.CommandAnnotationPluginVersion]. This alias will be removed in the next release. CommandAnnotationPluginVersion = metadata.CommandAnnotationPluginVersion // CommandAnnotationPluginInvalid is added to any stub command // added by AddPluginCommandStubs for an invalid command (that // is, one which failed it's candidate test) and contains the // reason for the failure. + // + // Deprecated: use [metadata.CommandAnnotationPluginInvalid]. This alias will be removed in the next release. CommandAnnotationPluginInvalid = metadata.CommandAnnotationPluginInvalid // CommandAnnotationPluginCommandPath is added to overwrite the // command path for a plugin invocation. + // + // Deprecated: use [metadata.CommandAnnotationPluginCommandPath]. This alias will be removed in the next release. CommandAnnotationPluginCommandPath = metadata.CommandAnnotationPluginCommandPath ) From 4881921784a4467ea7bffe025dde082910b8ade9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 16:52:05 +0000 Subject: [PATCH 049/193] build(deps): bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] (cherry picked from commit f2af519f2e66f93c6424190a2f88d4a4b11e55b2) Signed-off-by: Sebastiaan van Stijn --- .github/workflows/build.yml | 4 ++-- .github/workflows/codeql.yml | 2 +- .github/workflows/e2e.yml | 2 +- .github/workflows/test.yml | 2 +- .github/workflows/validate.yml | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 30bb4e0b027a..fa2fc9648bb9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Create matrix id: platforms @@ -143,7 +143,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Create matrix id: platforms diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8c6e1d7b9d80..161db5196cee 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -46,7 +46,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 2 # CodeQL 2.16.4's auto-build added support for multi-module repositories, diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 87433ec00704..7ecdb039c15c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -44,7 +44,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Update daemon.json run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 854fe9e88cbc..31113fb7886d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: ${{ env.GOPATH }}/src/github.com/docker/cli - diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 9bd443c13d77..63b688bcc696 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -48,7 +48,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Generate shell: 'script --return --quiet --command "bash {0}"' @@ -74,7 +74,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Run shell: 'script --return --quiet --command "bash {0}"' From 31fa40cb258a3696147f4ab64f4cb3f243704786 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 5 Aug 2025 23:23:38 +0200 Subject: [PATCH 050/193] docs: add deprecated `bind-nonrecursive` option for `--mount` The `bind-nonrecursive` option was replaced with the [`bind-recursive`] option (see [cli-4316], [cli-4671]), but the deprecated docs was not updated to mention. Based on abfe4d46296e3d96628ac748fb2093f08831e9ed in master [`bind-recursive`]: https://docs.docker.com/engine/storage/bind-mounts/#recursive-mounts [cli-4316]: https://github.com/docker/cli/pull/4316 [cli-4671]: https://github.com/docker/cli/pull/4671 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit abfe4d46296e3d96628ac748fb2093f08831e9ed) Signed-off-by: Sebastiaan van Stijn --- docs/deprecated.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/deprecated.md b/docs/deprecated.md index 74aab4643e64..a734d16aaded 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -64,6 +64,7 @@ The following table provides an overview of the current status of deprecated fea | Removed | [`Container` and `ContainerConfig` fields in Image inspect](#container-and-containerconfig-fields-in-image-inspect) | v25.0 | v26.0 | | Removed | [Deprecate legacy API versions](#deprecate-legacy-api-versions) | v25.0 | v26.0 | | Removed | [Container short ID in network Aliases field](#container-short-id-in-network-aliases-field) | v25.0 | v26.0 | +| Deprecated | [Mount `bind-nonrecursive` option](#mount-bind-nonrecursive-option) | v25.0 | v29.0 | | Removed | [IsAutomated field, and `is-automated` filter on `docker search`](#isautomated-field-and-is-automated-filter-on-docker-search) | v25.0 | v28.2 | | Removed | [logentries logging driver](#logentries-logging-driver) | v24.0 | v25.0 | | Removed | [OOM-score adjust for the daemon](#oom-score-adjust-for-the-daemon) | v24.0 | v25.0 | @@ -415,6 +416,26 @@ A new field `DNSNames` containing the container name (if one was specified), the hostname, the network aliases, as well as the container short ID, has been introduced in v25.0 and should be used instead of the `Aliases` field. +### Mount `bind-nonrecursive` option + +**Deprecated in release: v25.0** +**Scheduled for removal in release: v29.0** + +The `bind-nonrecursive` option was replaced with the [`bind-recursive`] +option (see [cli-4316], [cli-4671]). The option was still accepted, but +printed a deprecation warning: + +```console +bind-nonrecursive is deprecated, use bind-recursive=disabled instead +``` + +In the v29.0 release, this warning will be removed and returned as an error. +Users should use the equivalent `bind-recursive=disabled` option instead. + +[`bind-recursive`]: https://docs.docker.com/engine/storage/bind-mounts/#recursive-mounts +[cli-4316]: https://github.com/docker/cli/pull/4316 +[cli-4671]: https://github.com/docker/cli/pull/4671 + ### IsAutomated field, and `is-automated` filter on `docker search` **Deprecated in Release: v25.0** From c61c0cdb3c520a0daf85219145dc6986c96ce472 Mon Sep 17 00:00:00 2001 From: Albin Kerouanton Date: Thu, 14 Aug 2025 15:33:53 +0200 Subject: [PATCH 051/193] docs/deprecated: legacy links env vars Signed-off-by: Albin Kerouanton Co-authored-by: Sebastiaan van Stijn Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 5c76f7f2d8a46a6484b1533ca5552ee83a053d12) Signed-off-by: Sebastiaan van Stijn --- docs/deprecated.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/deprecated.md b/docs/deprecated.md index a734d16aaded..e2054870bd5b 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -53,6 +53,7 @@ The following table provides an overview of the current status of deprecated fea | Status | Feature | Deprecated | Remove | |------------|------------------------------------------------------------------------------------------------------------------------------------|------------|--------| +| Deprecated | [Legacy links environment variables](#legacy-links-environment-variables) | v28.4 | v30.0 | | Deprecated | [Special handling for quoted values for TLS flags](#special-handling-for-quoted-values-for-tls-flags) | v28.4 | v29.0 | | Deprecated | [Empty/nil fields in image Config from inspect API](#emptynil-fields-in-image-config-from-inspect-api) | v28.3 | v29.0 | | Deprecated | [Configuration for pushing non-distributable artifacts](#configuration-for-pushing-non-distributable-artifacts) | v28.0 | v29.0 | @@ -123,6 +124,25 @@ The following table provides an overview of the current status of deprecated fea | Removed | [`--run` flag on `docker commit`](#--run-flag-on-docker-commit) | v0.10 | v1.13 | | Removed | [Three arguments form in `docker import`](#three-arguments-form-in-docker-import) | v0.6.7 | v1.12 | +### Legacy links environment variables + +**Deprecated in Release: v28.4** +**Disabled by default in Release: v29.0** +**Target For Removal In Release: v30.0** + +Containers attached to the default bridge network can specify "legacy links" (e.g. +using `--links` on the CLI) to get access to other containers attached to that +network. The linking container (i.e., the container created with `--links`) automatically +gets environment variables that specify the IP address and port mappings of the linked +container. However, these environment variables are prefixed with the linked +container's names, making them impractical. + +Starting with Docker v29.0, these environment variables are no longer set by +default. Users who still depend on them can start Docker Engine with the +environment variable `DOCKER_KEEP_DEPRECATED_LEGACY_LINKS_ENV_VARS=1` set. + +Support for legacy links environment variables, as well as the `DOCKER_KEEP_DEPRECATED_LEGACY_LINKS_ENV_VARS` +will be removed in Docker Engine v30.0. ### Special handling for quoted values for TLS flags From 8f8d39c35eff43dd7828f9b4f995a557d1e1fbbc Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 19 Aug 2025 14:26:05 +0200 Subject: [PATCH 052/193] docs: deprecated: fix formatting of deprecated/removed in - Use sentence-case to follow our docs guidelines. - Add newlines to prevent these being rendered on a single line. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 1d571d178d3ff2986068881af0b442ce8fb3f214) Signed-off-by: Sebastiaan van Stijn --- docs/deprecated.md | 298 +++++++++++++++++++++++++-------------------- 1 file changed, 165 insertions(+), 133 deletions(-) diff --git a/docs/deprecated.md b/docs/deprecated.md index e2054870bd5b..80eebd5d8a0b 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -126,9 +126,11 @@ The following table provides an overview of the current status of deprecated fea ### Legacy links environment variables -**Deprecated in Release: v28.4** -**Disabled by default in Release: v29.0** -**Target For Removal In Release: v30.0** +**Deprecated in release: v28.4** + +**Disabled by default in release: v29.0** + +**Target for removal in release: v30.0** Containers attached to the default bridge network can specify "legacy links" (e.g. using `--links` on the CLI) to get access to other containers attached to that @@ -146,8 +148,9 @@ will be removed in Docker Engine v30.0. ### Special handling for quoted values for TLS flags -**Deprecated in Release: v28.4** -**Target For Removal In Release: v29.0** +**Deprecated in release: v28.4** + +**Target for removal in release: v29.0** The `--tlscacert`, `--tlscert`, and `--tlskey` command-line flags had non-standard behavior for handling values contained in quotes (`"` or `'`). @@ -178,8 +181,9 @@ above. ### Empty/nil fields in image Config from inspect API -**Deprecated in Release: v28.3** -**Target For Removal In Release: v29.0** +**Deprecated in release: v28.3** + +**Target for removal in release: v29.0** The `Config` field returned by `docker image inspect` (and the `GET /images/{name}/json` API endpoint) currently includes certain fields even when they are empty or nil. @@ -205,8 +209,9 @@ API version for backward compatibility. ### Configuration for pushing non-distributable artifacts -**Deprecated in Release: v28.0** -**Target For Removal In Release: v29.0** +**Deprecated in release: v28.0** + +**Target for removal in release: v29.0** Non-distributable artifacts (also called foreign layers) were introduced in docker v1.12 to accommodate Windows images for which the EULA did not allow @@ -244,7 +249,7 @@ entirely. ### `--time` option on `docker stop` and `docker restart` -**Deprecated in Release: v28.0** +**Deprecated in release: v28.0** The `--time` option for the `docker stop`, `docker container stop`, `docker restart`, and `docker container restart` commands has been renamed to `--timeout` for @@ -254,8 +259,9 @@ Users are encouraged to migrate to using the `--timeout` option instead. ### Non-standard fields in image inspect -**Deprecated in Release: v27.0** -**Removed In Release: v28.2** +**Deprecated in release: v27.0** + +**Removed in release: v28.2** The `Config` field returned shown in `docker image inspect` (and as returned by the `GET /images/{name}/json` API endpoint) returns additional fields that are @@ -289,9 +295,11 @@ They continue to be included when using clients that use an older API version: ### Graphdriver plugins (experimental) -**Deprecated in Release: v27.0** -**Disabled by default in Release: v27.0** -**Target For Removal In Release: v28.0** +**Deprecated in**: v27.0**. + +**Disabled by default in release: v27.0** + +**Target for removal in release: v28.0** [Graphdriver plugins](https://github.com/docker/cli/blob/v26.1.4/docs/extend/plugins_graphdriver.md) were an experimental feature that allowed extending the Docker Engine with custom @@ -305,8 +313,10 @@ and a custom [snapshotter](https://github.com/containerd/containerd/tree/v1.7.18 ### API CORS headers -**Deprecated in Release: v27.0** -**Disabled by default in Release: v27.0** +**Deprecated in release: v27.0** + +**Disabled by default in release: v27.0** + **Removed in release: v28.0** The `api-cors-header` configuration option for the Docker daemon is insecure, @@ -326,8 +336,9 @@ If you need to access the API through a browser, use a reverse proxy. ### Unauthenticated TCP connections -**Deprecated in Release: v26.0** -**Target For Removal In Release: v28.0** +**Deprecated in release: v26.0** + +**Target for removal in release: v28.0** Configuring the Docker daemon to listen on a TCP address will require mandatory TLS verification. This change aims to ensure secure communication by preventing @@ -353,8 +364,9 @@ configuring TLS (or SSH) for the Docker daemon, refer to ### `Container` and `ContainerConfig` fields in Image inspect -**Deprecated in Release: v25.0** -**Removed In Release: v26.0** +**Deprecated in release: v25.0** + +**Removed in release: v26.0** The `Container` and `ContainerConfig` fields returned by `docker inspect` are mostly an implementation detail of the classic (non-BuildKit) image builder. @@ -366,8 +378,9 @@ you can obtain it from the `Config` field. ### Deprecate legacy API versions -**Deprecated in Release: v25.0** -**Target For Removal In Release: v26.0** +**Deprecated in release: v25.0** + +**Target for removal in release: v26.0** The Docker daemon provides a versioned API for backward compatibility with old clients. Docker clients can perform API-version negotiation to select the most @@ -423,8 +436,9 @@ old clients, and those clients must be supported. ### Container short ID in network Aliases field -**Deprecated in Release: v25.0** -**Removed In Release: v26.0** +**Deprecated in release: v25.0** + +**Removed in release: v26.0** The `Aliases` field returned by `docker inspect` contains the container short ID once the container is started. This behavior is deprecated in v25.0 but @@ -439,6 +453,7 @@ introduced in v25.0 and should be used instead of the `Aliases` field. ### Mount `bind-nonrecursive` option **Deprecated in release: v25.0** + **Scheduled for removal in release: v29.0** The `bind-nonrecursive` option was replaced with the [`bind-recursive`] @@ -458,8 +473,9 @@ Users should use the equivalent `bind-recursive=disabled` option instead. ### IsAutomated field, and `is-automated` filter on `docker search` -**Deprecated in Release: v25.0** -**Removed In Release: v28.2** +**Deprecated in release: v25.0** + +**Removed in release: v28.2** The `is_automated` field has been deprecated by Docker Hub's search API. Consequently, the `IsAutomated` field in image search will always be set @@ -472,8 +488,9 @@ templating has been removed in v28.2. ### Logentries logging driver -**Deprecated in Release: v24.0** -**Removed in Release: v25.0** +**Deprecated in release: v24.0** + +**Removed in release: v25.0** The logentries service SaaS was shut down on November 15, 2022, rendering this logging driver non-functional. Users should no longer use this logging @@ -483,8 +500,9 @@ after upgrading. ### OOM-score adjust for the daemon -**Deprecated in Release: v24.0** -**Removed in Release: v25.0** +**Deprecated in release: v24.0** + +**Removed in release: v25.0** The `oom-score-adjust` option was added to prevent the daemon from being OOM-killed before other processes. This option was mostly added as a @@ -503,8 +521,9 @@ the daemon. ### BuildKit build information -**Deprecated in Release: v23.0** -**Removed in Release: v24.0** +**Deprecated in release: v23.0** + +**Removed in release: v24.0** [Build information](https://github.com/moby/buildkit/blob/v0.11/docs/buildinfo.md) structures have been introduced in [BuildKit v0.10.0](https://github.com/moby/buildkit/releases/tag/v0.10.0) @@ -515,7 +534,7 @@ information is also embedded into the image configuration if one is generated. ### Legacy builder for Linux images -**Deprecated in Release: v23.0** +**Deprecated in release: v23.0** Docker v23.0 now uses BuildKit by default to build Linux images, and uses the [Buildx](https://docs.docker.com/buildx/working-with-buildx/) CLI component for @@ -546,7 +565,7 @@ you to report issues in the [BuildKit issue tracker on GitHub](https://github.co ### Legacy builder fallback -**Deprecated in Release: v23.0** +**Deprecated in release: v23.0** [Docker v23.0 now uses BuildKit by default to build Linux images](#legacy-builder-for-linux-images), which requires the Buildx component to build images with BuildKit. There may be @@ -592,7 +611,7 @@ be possible in a future release. ### Btrfs storage driver on CentOS 7 and RHEL 7 -**Removed in Release: v23.0** +**Removed in release: v23.0** The `btrfs` storage driver on CentOS and RHEL was provided as a technology preview by CentOS and RHEL, but has been deprecated since the [Red Hat Enterprise Linux 7.4 release](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/storage_administration_guide/ch-btrfs), @@ -604,9 +623,9 @@ of Docker will no longer provide this driver. ### Support for encrypted TLS private keys -**Deprecated in Release: v20.10** +**Deprecated in release: v20.10** -**Removed in Release: v23.0** +**Removed in release: v23.0** Use of encrypted TLS private keys has been deprecated, and has been removed. Golang has deprecated support for legacy PEM encryption (as specified in @@ -620,8 +639,9 @@ to decrypt the private key, and store it un-encrypted to continue using it. ### Kubernetes stack and context support -**Deprecated in Release: v20.10** -**Removed in Release: v23.0** +**Deprecated in release: v20.10** + +**Removed in release: v23.0** Following the deprecation of [Compose on Kubernetes](https://github.com/docker/compose-on-kubernetes), support for Kubernetes in the `stack` and `context` commands has been removed from @@ -649,8 +669,9 @@ CLI configuration file are no longer used, and ignored. ### Pulling images from non-compliant image registries -**Deprecated in Release: v20.10** -**Removed in Release: v28.2** +**Deprecated in release: v20.10** + +**Removed in release: v28.2** Docker Engine v20.10 and up includes optimizations to verify if images in the local image cache need updating before pulling, preventing the Docker Engine @@ -679,8 +700,9 @@ no longer needed. ### Linux containers on Windows (LCOW) (experimental) -**Deprecated in Release: v20.10** -**Removed in Release: v23.0** +**Deprecated in release: v20.10** + +**Removed in release: v23.0** The experimental feature to run Linux containers on Windows (LCOW) was introduced as a technical preview in Docker 17.09. While many enhancements were made after @@ -692,7 +714,7 @@ Developers who want to run Linux workloads on a Windows host are encouraged to u ### BLKIO weight options with cgroups v1 -**Deprecated in Release: v20.10** +**Deprecated in release: v20.10** Specifying blkio weight (`docker run --blkio-weight` and `docker run --blkio-weight-device`) is now marked as deprecated when using cgroups v1 because the corresponding features @@ -702,8 +724,9 @@ When using cgroups v2, the `--blkio-weight` options are implemented using ### Kernel memory limit -**Deprecated in Release: v20.10** -**Removed in Release: v23.0** +**Deprecated in release: v20.10** + +**Removed in release: v23.0** Specifying kernel memory limit (`docker run --kernel-memory`) is no longer supported because the [Linux kernel deprecated `kmem.limit_in_bytes` in v5.4](https://github.com/torvalds/linux/commit/0158115f702b0ba208ab0b5adf44cae99b3ebcc7). @@ -729,8 +752,9 @@ take no effect. ### Classic Swarm and overlay networks using cluster store -**Deprecated in Release: v20.10** -**Removed in Release: v23.0** +**Deprecated in release: v20.10** + +**Removed in release: v23.0** Standalone ("classic") Swarm has been deprecated, and with that the use of overlay networks using an external key/value store. The corresponding`--cluster-advertise`, @@ -738,8 +762,9 @@ networks using an external key/value store. The corresponding`--cluster-advertis ### Support for legacy `~/.dockercfg` configuration files -**Deprecated in Release: v20.10** -**Removed in Release: v23.0** +**Deprecated in release: v20.10** + +**Removed in release: v23.0** The Docker CLI up until v1.7.0 used the `~/.dockercfg` file to store credentials after authenticating to a registry (`docker login`). Docker v1.7.0 replaced this @@ -754,9 +779,9 @@ been removed. ### Configuration options for experimental CLI features -**Deprecated in Release: v19.03** +**Deprecated in release: v19.03** -**Removed in Release: v23.0** +**Removed in release: v23.0** The `DOCKER_CLI_EXPERIMENTAL` environment variable and the corresponding `experimental` field in the CLI configuration file are deprecated. Experimental features are @@ -768,13 +793,13 @@ format. ### CLI plugins support -**Deprecated in Release: v20.10** +**Deprecated in release: v20.10** CLI Plugin API is now marked as deprecated. ### Dockerfile legacy `ENV name value` syntax -**Deprecated in Release: v20.10** +**Deprecated in release: v20.10** The Dockerfile `ENV` instruction allows values to be set using either `ENV name=value` or `ENV name value`. The latter (`ENV name value`) form can be ambiguous, for example, @@ -798,8 +823,9 @@ ENV ONE="" TWO="" THREE="world" ### `docker build --stream` flag (experimental) -**Deprecated in Release: v20.10** -**Removed in Release: v20.10** +**Deprecated in release: v20.10** + +**Removed in release: v20.10** Docker v17.07 introduced an experimental `--stream` flag on `docker build` which allowed the build-context to be incrementally sent to the daemon, instead of @@ -815,8 +841,9 @@ files. ### `fluentd-async-connect` log opt -**Deprecated in Release: v20.10** -**Removed in Release: v28.0** +**Deprecated in release: v20.10** + +**Removed in release: v28.0** The `--log-opt fluentd-async-connect` option for the fluentd logging driver is [deprecated in favor of `--log-opt fluentd-async`](https://github.com/moby/moby/pull/39086). @@ -831,11 +858,11 @@ for the old option has been removed. ### Pushing and pulling with image manifest v2 schema 1 -**Deprecated in Release: v19.03** +**Deprecated in release: v19.03** -**Disabled by default in Release: v26.0** +**Disabled by default in release: v26.0** -**Removed in Release: v28.2** +**Removed in release: v28.2** The image manifest [v2 schema 1](https://distribution.github.io/distribution/spec/deprecated-schema-v1/) @@ -860,9 +887,9 @@ More information at https://docs.docker.com/go/deprecated-image-specs/ ### `docker engine` subcommands -**Deprecated in Release: v19.03** +**Deprecated in release: v19.03** -**Removed in Release: v20.10** +**Removed in release: v20.10** The `docker engine activate`, `docker engine check`, and `docker engine update` provided an alternative installation method to upgrade Docker Community engines @@ -875,9 +902,9 @@ standard package managers. ### Top-level `docker deploy` subcommand (experimental) -**Deprecated in Release: v19.03** +**Deprecated in release: v19.03** -**Removed in Release: v20.10** +**Removed in release: v20.10** The top-level `docker deploy` command (using the "Docker Application Bundle" (.dab) file format was introduced as an experimental feature in Docker 1.13 / @@ -886,9 +913,9 @@ subcommand. ### `docker stack deploy` using "dab" files (experimental) -**Deprecated in Release: v19.03** +**Deprecated in release: v19.03** -**Removed in Release: v20.10** +**Removed in release: v20.10** With no development being done on this feature, and no active use of the file format, support for the DAB file format and the top-level `docker deploy` command @@ -897,8 +924,9 @@ using compose files. ### Support for the `overlay2.override_kernel_check` storage option -**Deprecated in Release: v19.03** -**Removed in Release: v24.0** +**Deprecated in release: v19.03** + +**Removed in release: v24.0** This daemon configuration option disabled the Linux kernel version check used to detect if the kernel supported OverlayFS with multiple lower dirs, which is @@ -908,8 +936,9 @@ option was no longer used. ### AuFS storage driver -**Deprecated in Release: v19.03** -**Removed in Release: v24.0** +**Deprecated in release: v19.03** + +**Removed in release: v24.0** The `aufs` storage driver is deprecated in favor of `overlay2`, and has been removed in a Docker Engine v24.0. Users of the `aufs` storage driver must @@ -927,8 +956,9 @@ maintenance of the `aufs` storage driver. ### Legacy overlay storage driver -**Deprecated in Release: v18.09** -**Removed in Release: v24.0** +**Deprecated in release: v18.09** + +**Removed in release: v24.0** The `overlay` storage driver is deprecated in favor of the `overlay2` storage driver, which has all the benefits of `overlay`, without its limitations (excessive @@ -943,9 +973,11 @@ backported), there is no reason to keep maintaining the `overlay` storage driver ### Device mapper storage driver -**Deprecated in Release: v18.09** -**Disabled by default in Release: v23.0** -**Removed in Release: v25.0** +**Deprecated in release: v18.09** + +**Disabled by default in release: v23.0** + +**Removed in release: v25.0** The `devicemapper` storage driver is deprecated in favor of `overlay2`, and has been removed in Docker Engine v25.0. Users of the `devicemapper` storage driver @@ -961,9 +993,9 @@ is no reason to continue maintenance of the `devicemapper` storage driver. ### Use of reserved namespaces in engine labels -**Deprecated in Release: v18.06** +**Deprecated in release: v18.06** -**Removed In Release: v20.10** +**Removed in release: v20.10** The namespaces `com.docker.*`, `io.docker.*`, and `org.dockerproject.*` in engine labels were always documented to be reserved, but there was never any enforcement. @@ -975,7 +1007,7 @@ use, and will error instead in v20.10 and above. **Disabled In Release: v17.12** -**Removed In Release: v19.03** +**Removed in release: v19.03** The `--disable-legacy-registry` flag was disabled in Docker 17.12 and will print an error when used. For this error to be printed, the flag itself is still present, @@ -983,9 +1015,9 @@ but hidden. The flag has been removed in Docker 19.03. ### Interacting with V1 registries -**Disabled By Default In Release: v17.06** +**Disabled by default in release: v17.06** -**Removed In Release: v17.12** +**Removed in release: v17.12** Version 1.8.3 added a flag (`--disable-legacy-registry=false`) which prevents the Docker daemon from `pull`, `push`, and `login` operations against v1 @@ -1002,7 +1034,7 @@ start when set. ### Asynchronous `service create` and `service update` as default -**Deprecated In Release: v17.05** +**Deprecated in release: v17.05** **Disabled by default in release: [v17.10](https://github.com/docker/docker-ce/releases/tag/v17.10.0-ce)** @@ -1016,9 +1048,9 @@ and `docker service scale` in Docker 17.10. ### `-g` and `--graph` flags on `dockerd` -**Deprecated In Release: v17.05** +**Deprecated in release: v17.05** -**Removed In Release: v23.0** +**Removed in release: v23.0** The `-g` or `--graph` flag for the `dockerd` or `docker daemon` command was used to indicate the directory in which to store persistent data and resource @@ -1027,9 +1059,9 @@ flag. These flags were deprecated and hidden in v17.05, and removed in v23.0. ### Top-level network properties in NetworkSettings -**Deprecated In Release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** +**Deprecated in release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** -**Target For Removal In Release: v17.12** +**Target for removal in release: v17.12** When inspecting a container, `NetworkSettings` contains top-level information about the default ("bridge") network; @@ -1046,18 +1078,18 @@ information. ### `filter` option for `/images/json` endpoint -**Deprecated In Release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** +**Deprecated in release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** -**Removed In Release: v20.10** +**Removed in release: v20.10** The `filter` option to filter the list of image by reference (name or name:tag) is now implemented as a regular filter, named `reference`. ### `repository:shortid` image references -**Deprecated In Release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** +**Deprecated in release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** -**Removed In Release: v17.12** +**Removed in release: v17.12** The `repository:shortid` syntax for referencing images is very little used, collides with tag references, and can be confused with digest references. @@ -1067,32 +1099,32 @@ in Docker 17.12. ### `docker daemon` subcommand -**Deprecated In Release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** +**Deprecated in release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** -**Removed In Release: v17.12** +**Removed in release: v17.12** The daemon is moved to a separate binary (`dockerd`), and should be used instead. ### Duplicate keys with conflicting values in engine labels -**Deprecated In Release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** +**Deprecated in release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** -**Removed In Release: v17.12** +**Removed in release: v17.12** When setting duplicate keys with conflicting values, an error will be produced, and the daemon will fail to start. ### `MAINTAINER` in Dockerfile -**Deprecated In Release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** +**Deprecated in release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** `MAINTAINER` was an early very limited form of `LABEL` which should be used instead. ### API calls without a version -**Deprecated In Release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** +**Deprecated in release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** -**Target For Removal In Release: v17.12** +**Target for removal in release: v17.12** API versions should be supplied to all API calls to ensure compatibility with future Engine versions. Instead of just requesting, for example, the URL @@ -1100,9 +1132,9 @@ future Engine versions. Instead of just requesting, for example, the URL ### Backing filesystem without `d_type` support for overlay/overlay2 -**Deprecated In Release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** +**Deprecated in release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** -**Removed In Release: v17.12** +**Removed in release: v17.12** The overlay and overlay2 storage driver does not work as expected if the backing filesystem does not support `d_type`. For example, XFS does not support `d_type` @@ -1116,18 +1148,18 @@ Refer to [#27358](https://github.com/docker/docker/issues/27358) for details. ### `--automated` and `--stars` flags on `docker search` -**Deprecated in Release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** +**Deprecated in release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** -**Removed In Release: v20.10** +**Removed in release: v20.10** The `docker search --automated` and `docker search --stars` options are deprecated. Use `docker search --filter=is-automated=` and `docker search --filter=stars=...` instead. ### `-h` shorthand for `--help` -**Deprecated In Release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** +**Deprecated in release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** -**Target For Removal In Release: v17.09** +**Target for removal in release: v17.09** The shorthand (`-h`) is less common than `--help` on Linux and cannot be used on all subcommands (due to it conflicting with, e.g. `-h` / `--hostname` on @@ -1136,58 +1168,58 @@ on all subcommands (due to it conflicting with, e.g. `-h` / `--hostname` on ### `-e` and `--email` flags on `docker login` -**Deprecated In Release: [v1.11.0](https://github.com/docker/docker/releases/tag/v1.11.0)** +**Deprecated in release: [v1.11.0](https://github.com/docker/docker/releases/tag/v1.11.0)** -**Removed In Release: [v17.06](https://github.com/docker/docker-ce/releases/tag/v17.06.0-ce)** +**Removed in release: [v17.06](https://github.com/docker/docker-ce/releases/tag/v17.06.0-ce)** The `docker login` no longer automatically registers an account with the target registry if the given username doesn't exist. Due to this change, the email flag is no longer required, and will be deprecated. ### Separator (`:`) of `--security-opt` flag on `docker run` -**Deprecated In Release: [v1.11.0](https://github.com/docker/docker/releases/tag/v1.11.0)** +**Deprecated in release: [v1.11.0](https://github.com/docker/docker/releases/tag/v1.11.0)** -**Target For Removal In Release: v17.06** +**Target for removal in release: v17.06** The flag `--security-opt` doesn't use the colon separator (`:`) anymore to divide keys and values, it uses the equal symbol (`=`) for consistency with other similar flags, like `--storage-opt`. ### Ambiguous event fields in API -**Deprecated In Release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)** +**Deprecated in release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)** The fields `ID`, `Status` and `From` in the events API have been deprecated in favor of a more rich structure. See the events API documentation for the new format. ### `-f` flag on `docker tag` -**Deprecated In Release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)** +**Deprecated in release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)** -**Removed In Release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** +**Removed in release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** To make tagging consistent across the various `docker` commands, the `-f` flag on the `docker tag` command is deprecated. It is no longer necessary to specify `-f` to move a tag from one image to another. Nor will `docker` generate an error if the `-f` flag is missing and the specified tag is already in use. ### HostConfig at API container start -**Deprecated In Release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)** +**Deprecated in release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)** -**Removed In Release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** +**Removed in release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** Passing an `HostConfig` to `POST /containers/{name}/start` is deprecated in favor of defining it at container creation (`POST /containers/create`). ### `--before` and `--since` flags on `docker ps` -**Deprecated In Release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)** +**Deprecated in release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)** -**Removed In Release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** +**Removed in release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** The `docker ps --before` and `docker ps --since` options are deprecated. Use `docker ps --filter=before=...` and `docker ps --filter=since=...` instead. ### Driver-specific log tags -**Deprecated In Release: [v1.9.0](https://github.com/docker/docker/releases/tag/v1.9.0)** +**Deprecated in release: [v1.9.0](https://github.com/docker/docker/releases/tag/v1.9.0)** -**Removed In Release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** +**Removed in release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** Log tags are now generated in a standard way across different logging drivers. Because of which, the driver specific log tag options `syslog-tag`, `gelf-tag` and @@ -1199,9 +1231,9 @@ $ docker --log-driver=syslog --log-opt tag="{{.ImageName}}/{{.Name}}/{{.ID}}" ### Docker Content Trust ENV passphrase variables name change -**Deprecated In Release: [v1.9.0](https://github.com/docker/docker/releases/tag/v1.9.0)** +**Deprecated in release: [v1.9.0](https://github.com/docker/docker/releases/tag/v1.9.0)** -**Removed In Release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** +**Removed in release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** Since 1.9, Docker Content Trust Offline key has been renamed to Root key and the Tagging key has been renamed to Repository key. Due to this renaming, we're also changing the corresponding environment variables @@ -1210,25 +1242,25 @@ Since 1.9, Docker Content Trust Offline key has been renamed to Root key and the ### `/containers/(id or name)/copy` endpoint -**Deprecated In Release: [v1.8.0](https://github.com/docker/docker/releases/tag/v1.8.0)** +**Deprecated in release: [v1.8.0](https://github.com/docker/docker/releases/tag/v1.8.0)** -**Removed In Release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** +**Removed in release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** The endpoint `/containers/(id or name)/copy` is deprecated in favor of `/containers/(id or name)/archive`. ### LXC built-in exec driver -**Deprecated In Release: [v1.8.0](https://github.com/docker/docker/releases/tag/v1.8.0)** +**Deprecated in release: [v1.8.0](https://github.com/docker/docker/releases/tag/v1.8.0)** -**Removed In Release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)** +**Removed in release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)** The built-in LXC execution driver, the lxc-conf flag, and API fields have been removed. ### Old Command Line Options -**Deprecated In Release: [v1.8.0](https://github.com/docker/docker/releases/tag/v1.8.0)** +**Deprecated in release: [v1.8.0](https://github.com/docker/docker/releases/tag/v1.8.0)** -**Removed In Release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)** +**Removed in release: [v1.10.0](https://github.com/docker/docker/releases/tag/v1.10.0)** The flags `-d` and `--daemon` are deprecated. Use the separate `dockerd` binary instead. @@ -1272,34 +1304,34 @@ The following double-dash options are deprecated and have no replacement: - `docker ps --before-id` - `docker search --trusted` -**Deprecated In Release: [v1.5.0](https://github.com/docker/docker/releases/tag/v1.5.0)** +**Deprecated in release: [v1.5.0](https://github.com/docker/docker/releases/tag/v1.5.0)** -**Removed In Release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** +**Removed in release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** The single-dash (`-help`) was removed, in favor of the double-dash `--help` ### `--api-enable-cors` flag on `dockerd` -**Deprecated In Release: [v1.6.0](https://github.com/docker/docker/releases/tag/v1.6.0)** +**Deprecated in release: [v1.6.0](https://github.com/docker/docker/releases/tag/v1.6.0)** -**Removed In Release: [v17.09](https://github.com/docker/docker-ce/releases/tag/v17.09.0-ce)** +**Removed in release: [v17.09](https://github.com/docker/docker-ce/releases/tag/v17.09.0-ce)** The flag `--api-enable-cors` is deprecated since v1.6.0. Use the flag `--api-cors-header` instead. ### `--run` flag on `docker commit` -**Deprecated In Release: [v0.10.0](https://github.com/docker/docker/releases/tag/v0.10.0)** +**Deprecated in release: [v0.10.0](https://github.com/docker/docker/releases/tag/v0.10.0)** -**Removed In Release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** +**Removed in release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)** The flag `--run` of the `docker commit` command (and its short version `-run`) were deprecated in favor of the `--changes` flag that allows to pass `Dockerfile` commands. ### Three arguments form in `docker import` -**Deprecated In Release: [v0.6.7](https://github.com/docker/docker/releases/tag/v0.6.7)** +**Deprecated in release: [v0.6.7](https://github.com/docker/docker/releases/tag/v0.6.7)** -**Removed In Release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** +**Removed in release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)** The `docker import` command format `file|URL|- [REPOSITORY [TAG]]` is deprecated since November 2013. It's no longer supported. From be307b2925b2649094be371ba1b2bb9476e39726 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 19 Aug 2025 12:39:10 +0200 Subject: [PATCH 053/193] cli/command: TestRetrieveAuthTokenFromImage: don't decode authconfig Rewrite the test to not depend on registry.DecodeAuthConfig, which may be moved internal to the daemon as part of the modules transition. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit ae1727c41ef71f41f7a689a800c8009591ccf0d8) Signed-off-by: Sebastiaan van Stijn --- cli/command/registry_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/command/registry_test.go b/cli/command/registry_test.go index 4c6bb9f89abb..353bfb2fd8fa 100644 --- a/cli/command/registry_test.go +++ b/cli/command/registry_test.go @@ -185,9 +185,9 @@ func TestRetrieveAuthTokenFromImage(t *testing.T) { imageRef := path.Join(tc.prefix, remoteRef) actual, err := command.RetrieveAuthTokenFromImage(&cfg, imageRef) assert.NilError(t, err) - ac, err := registry.DecodeAuthConfig(actual) + expectedAuthCfg, err := registry.EncodeAuthConfig(tc.expectedAuthCfg) assert.NilError(t, err) - assert.Check(t, is.DeepEqual(*ac, tc.expectedAuthCfg)) + assert.Equal(t, actual, expectedAuthCfg) } }) } From 53ed958805457ab838d93caacd20c0e28100501a Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 6 Aug 2025 16:55:22 +0200 Subject: [PATCH 054/193] cli/command: remove AddTrustSigningFlags it was only used internally in a single location, so inline the code where it's used. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 8c2292797807ae6c7a324984a406e09d66a8ca48) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/push.go | 2 +- cli/command/trust.go | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/cli/command/image/push.go b/cli/command/image/push.go index cd79ff92894a..1ef31aedd7fd 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -57,7 +57,7 @@ func NewPushCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() flags.BoolVarP(&opts.all, "all-tags", "a", false, "Push all tags of an image to the repository") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output") - command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) + flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image signing") // Don't default to DOCKER_DEFAULT_PLATFORM env variable, always default to // pushing the image as-is. This also avoids forcing the platform selection diff --git a/cli/command/trust.go b/cli/command/trust.go index 65f2408585d8..c802a14e1f2f 100644 --- a/cli/command/trust.go +++ b/cli/command/trust.go @@ -8,8 +8,3 @@ import ( func AddTrustVerificationFlags(fs *pflag.FlagSet, v *bool, trusted bool) { fs.BoolVar(v, "disable-content-trust", !trusted, "Skip image verification") } - -// AddTrustSigningFlags adds "signing" flags to the provided flagset -func AddTrustSigningFlags(fs *pflag.FlagSet, v *bool, trusted bool) { - fs.BoolVar(v, "disable-content-trust", !trusted, "Skip image signing") -} From 7126bf7d22f4dfd0d4b3afe2de7ebb28d0e89c78 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 19 Aug 2025 18:30:46 +0200 Subject: [PATCH 055/193] [28.x] remove some uses of AddTrustVerificationFlags These were already removed in master, so adding an extra commit in the 28.x branch to remove their use. Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build.go | 2 +- cli/command/plugin/install.go | 2 +- cli/command/plugin/push.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/command/image/build.go b/cli/command/image/build.go index 6c6db76675ea..15448e7b4927 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -145,7 +145,7 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command { flags.SetAnnotation("target", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/buildx/build/#target"}) flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file") - command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled()) + flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") flags.SetAnnotation("platform", "version", []string{"1.38"}) diff --git a/cli/command/plugin/install.go b/cli/command/plugin/install.go index 33a7179edd17..31f123ddfeac 100644 --- a/cli/command/plugin/install.go +++ b/cli/command/plugin/install.go @@ -31,7 +31,7 @@ type pluginOptions struct { func loadPullFlags(dockerCli command.Cli, opts *pluginOptions, flags *pflag.FlagSet) { flags.BoolVar(&opts.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin") - command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) + flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") } func newInstallCommand(dockerCli command.Cli) *cobra.Command { diff --git a/cli/command/plugin/push.go b/cli/command/plugin/push.go index 573ae3d0ae99..f70e36910c8d 100644 --- a/cli/command/plugin/push.go +++ b/cli/command/plugin/push.go @@ -33,7 +33,7 @@ func newPushCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() - command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) + flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image signing") return cmd } From 4e00c31c71cbac90d8746f856cf1a17d24f5ec19 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 6 Aug 2025 16:57:22 +0200 Subject: [PATCH 056/193] cli/command: remove AddTrustVerificationFlags It was only used internally; inline it where used. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit c0fbbe05ca09ccbfed34d76287832ea30f05db05) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/create.go | 2 +- cli/command/container/run.go | 2 +- cli/command/image/pull.go | 2 +- cli/command/trust.go | 10 ---------- 4 files changed, 3 insertions(+), 13 deletions(-) delete mode 100644 cli/command/trust.go diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 9906f63b227e..4d58d00ae26e 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -87,7 +87,7 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { flags.Bool("help", false, "Print usage") command.AddPlatformFlag(flags, &options.platform) - command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled()) + flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") copts = addFlags(flags) addCompletions(cmd, dockerCli) diff --git a/cli/command/container/run.go b/cli/command/container/run.go index b86ad9d5b275..20c1f7ca4b0b 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -67,7 +67,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { flags.Bool("help", false, "Print usage") command.AddPlatformFlag(flags, &options.platform) - command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled()) + flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") copts = addFlags(flags) _ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys) diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index 235e3a7a1754..5e4b3736cab4 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -51,7 +51,7 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command { flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output") command.AddPlatformFlag(flags, &opts.platform) - command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) + flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) diff --git a/cli/command/trust.go b/cli/command/trust.go deleted file mode 100644 index c802a14e1f2f..000000000000 --- a/cli/command/trust.go +++ /dev/null @@ -1,10 +0,0 @@ -package command - -import ( - "github.com/spf13/pflag" -) - -// AddTrustVerificationFlags adds content trust flags to the provided flagset -func AddTrustVerificationFlags(fs *pflag.FlagSet, v *bool, trusted bool) { - fs.BoolVar(v, "disable-content-trust", !trusted, "Skip image verification") -} From 80884714dea5b0d764892e495a30cc13864326d8 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 6 Aug 2025 17:15:32 +0200 Subject: [PATCH 057/193] cli/command: remove AddPlatformFlag utility It was only used internally and has no external users. It should not be used for new uses, because it also adds a minimum API version constraint and a default from env-var, which must be evaluated for each individual use of such flags. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 7026e68a7195f2bd9a6b9b5c6ce0e5e38e20973d) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/create.go | 5 ++++- cli/command/container/opts.go | 10 ++++++++++ cli/command/container/run.go | 3 ++- cli/command/image/import.go | 2 +- cli/command/image/opts.go | 17 +++++++++++++++++ cli/command/image/pull.go | 2 +- cli/command/utils.go | 7 ------- 7 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 cli/command/image/opts.go diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 4d58d00ae26e..736ee568ea41 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -86,7 +86,10 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { // with hostname flags.Bool("help", false, "Print usage") - command.AddPlatformFlag(flags, &options.platform) + // TODO(thaJeztah): consider adding platform as "image create option" on containerOptions + addPlatformFlag(flags, &options.platform) + _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) + flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") copts = addFlags(flags) diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index 647dc5d51008..16b9176db9bc 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -141,6 +141,16 @@ 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 20c1f7ca4b0b..6c5748a67ae1 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -66,7 +66,8 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { // with hostname flags.Bool("help", false, "Print usage") - command.AddPlatformFlag(flags, &options.platform) + // TODO(thaJeztah): consider adding platform as "image create option" on containerOptions + addPlatformFlag(flags, &options.platform) flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") copts = addFlags(flags) diff --git a/cli/command/image/import.go b/cli/command/image/import.go index a55ea4ab5bca..f0da5c6a4752 100644 --- a/cli/command/image/import.go +++ b/cli/command/image/import.go @@ -47,7 +47,7 @@ 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") - command.AddPlatformFlag(flags, &options.platform) + addPlatformFlag(flags, &options.platform) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) return cmd diff --git a/cli/command/image/opts.go b/cli/command/image/opts.go new file mode 100644 index 000000000000..37378b6ece65 --- /dev/null +++ b/cli/command/image/opts.go @@ -0,0 +1,17 @@ +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/image/pull.go b/cli/command/image/pull.go index 5e4b3736cab4..c916bad24f3f 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -50,7 +50,7 @@ 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") - command.AddPlatformFlag(flags, &opts.platform) + addPlatformFlag(flags, &opts.platform) flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) diff --git a/cli/command/utils.go b/cli/command/utils.go index f024ffd2789d..5987b537172e 100644 --- a/cli/command/utils.go +++ b/cli/command/utils.go @@ -15,7 +15,6 @@ import ( "github.com/docker/cli/internal/prompt" "github.com/docker/docker/api/types/filters" "github.com/pkg/errors" - "github.com/spf13/pflag" ) const ErrPromptTerminated = prompt.ErrTerminated @@ -94,12 +93,6 @@ func PruneFilters(dockerCLI config.Provider, pruneFilters filters.Args) filters. return pruneFilters } -// AddPlatformFlag adds `platform` to a set of flags for API version 1.32 and later. -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"}) -} - // ValidateOutputPath validates the output paths of the "docker cp" command. func ValidateOutputPath(path string) error { dir := filepath.Dir(filepath.Clean(path)) From 66c3dbdfedee0875f1ae9df4c1339c5f7a4bc475 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 19 Aug 2025 14:57:00 +0200 Subject: [PATCH 058/193] cli/registry/client: deprecate and move internal Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 13010ba673fec3b71fe2eb648df3d0a0abb18e94) Signed-off-by: Sebastiaan van Stijn --- cli/command/manifest/annotate.go | 2 +- cli/command/manifest/client_test.go | 4 ++-- cli/command/manifest/push.go | 2 +- cli/registry/client/client_deprecated.go | 21 +++++++++++++++++++ .../registryclient}/client.go | 2 +- .../registryclient}/endpoint.go | 2 +- .../registryclient}/fetcher.go | 2 +- internal/test/cli.go | 2 +- 8 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 cli/registry/client/client_deprecated.go rename {cli/registry/client => internal/registryclient}/client.go (99%) rename {cli/registry/client => internal/registryclient}/endpoint.go (99%) rename {cli/registry/client => internal/registryclient}/fetcher.go (99%) diff --git a/cli/command/manifest/annotate.go b/cli/command/manifest/annotate.go index 0f2a10aade1e..cf9f21944517 100644 --- a/cli/command/manifest/annotate.go +++ b/cli/command/manifest/annotate.go @@ -9,7 +9,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/manifest/store" - registryclient "github.com/docker/cli/cli/registry/client" + "github.com/docker/cli/internal/registryclient" "github.com/docker/docker/api/types/registry" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" diff --git a/cli/command/manifest/client_test.go b/cli/command/manifest/client_test.go index 497251c70e16..16b7912ab6f5 100644 --- a/cli/command/manifest/client_test.go +++ b/cli/command/manifest/client_test.go @@ -5,7 +5,7 @@ import ( "github.com/distribution/reference" manifesttypes "github.com/docker/cli/cli/manifest/types" - "github.com/docker/cli/cli/registry/client" + "github.com/docker/cli/internal/registryclient" "github.com/docker/distribution" "github.com/opencontainers/go-digest" ) @@ -45,4 +45,4 @@ func (c *fakeRegistryClient) PutManifest(ctx context.Context, ref reference.Name return digest.Digest(""), nil } -var _ client.RegistryClient = &fakeRegistryClient{} +var _ registryclient.RegistryClient = &fakeRegistryClient{} diff --git a/cli/command/manifest/push.go b/cli/command/manifest/push.go index ccfa84cd6c5b..c772cf2c3396 100644 --- a/cli/command/manifest/push.go +++ b/cli/command/manifest/push.go @@ -10,7 +10,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/manifest/types" - registryclient "github.com/docker/cli/cli/registry/client" + "github.com/docker/cli/internal/registryclient" "github.com/docker/distribution" "github.com/docker/distribution/manifest/manifestlist" "github.com/docker/distribution/manifest/ocischema" diff --git a/cli/registry/client/client_deprecated.go b/cli/registry/client/client_deprecated.go new file mode 100644 index 000000000000..b3ffde57496d --- /dev/null +++ b/cli/registry/client/client_deprecated.go @@ -0,0 +1,21 @@ +package client // Deprecated: this package was only used internally and will be removed in the next release. + +import "github.com/docker/cli/internal/registryclient" + +// RegistryClient is a client used to communicate with a Docker distribution +// registry. +// +// Deprecated: this interface was only used internally and will be removed in the next release. +type RegistryClient = registryclient.RegistryClient + +// NewRegistryClient returns a new RegistryClient with a resolver +// +// Deprecated: this function was only used internally and will be removed in the next release. +func NewRegistryClient(resolver registryclient.AuthConfigResolver, userAgent string, insecure bool) registryclient.RegistryClient { + return registryclient.NewRegistryClient(resolver, userAgent, insecure) +} + +// AuthConfigResolver returns Auth Configuration for an index +// +// Deprecated: this type was only used internally and will be removed in the next release. +type AuthConfigResolver = registryclient.AuthConfigResolver diff --git a/cli/registry/client/client.go b/internal/registryclient/client.go similarity index 99% rename from cli/registry/client/client.go rename to internal/registryclient/client.go index f1fee951f305..6b88c5a2cb1e 100644 --- a/cli/registry/client/client.go +++ b/internal/registryclient/client.go @@ -1,4 +1,4 @@ -package client +package registryclient import ( "context" diff --git a/cli/registry/client/endpoint.go b/internal/registryclient/endpoint.go similarity index 99% rename from cli/registry/client/endpoint.go rename to internal/registryclient/endpoint.go index ce6e774969bd..59df91690a76 100644 --- a/cli/registry/client/endpoint.go +++ b/internal/registryclient/endpoint.go @@ -1,4 +1,4 @@ -package client +package registryclient import ( "context" diff --git a/cli/registry/client/fetcher.go b/internal/registryclient/fetcher.go similarity index 99% rename from cli/registry/client/fetcher.go rename to internal/registryclient/fetcher.go index 852bc86e1ea4..b36aab51cc85 100644 --- a/cli/registry/client/fetcher.go +++ b/internal/registryclient/fetcher.go @@ -1,4 +1,4 @@ -package client +package registryclient import ( "context" diff --git a/internal/test/cli.go b/internal/test/cli.go index 8b506e14149c..5a2d74b678dc 100644 --- a/internal/test/cli.go +++ b/internal/test/cli.go @@ -11,9 +11,9 @@ import ( "github.com/docker/cli/cli/context/docker" "github.com/docker/cli/cli/context/store" manifeststore "github.com/docker/cli/cli/manifest/store" - registryclient "github.com/docker/cli/cli/registry/client" "github.com/docker/cli/cli/streams" "github.com/docker/cli/cli/trust" + "github.com/docker/cli/internal/registryclient" "github.com/docker/docker/api" "github.com/docker/docker/client" notaryclient "github.com/theupdateframework/notary/client" From a746a99d978400df5bfffb7d7679cc189d469927 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 19 Aug 2025 23:11:17 +0200 Subject: [PATCH 059/193] cli/command: fix godoc links - Use versioned links to github.com/docker/docker packages - Fix links to RFC 4648, section 5 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 2d3b0b33b498a3b6ea143393c689336afe41d2f4) Signed-off-by: Sebastiaan van Stijn --- cli/command/registry.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/cli/command/registry.go b/cli/command/registry.go index 5940abec8aae..b332b32e9023 100644 --- a/cli/command/registry.go +++ b/cli/command/registry.go @@ -31,7 +31,7 @@ const ( // authConfigKey is the key used to store credentials for Docker Hub. It is // a copy of [registry.IndexServer]. // -// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker/registry#IndexServer +// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#IndexServer const authConfigKey = "https://index.docker.io/v1/" // RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info @@ -68,6 +68,8 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf // // It is similar to [registry.ResolveAuthConfig], but uses the credentials- // store, instead of looking up credentials from a map. +// +// [registry.ResolveAuthConfig]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#ResolveAuthConfig func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInfo) registrytypes.AuthConfig { configKey := index.Name if index.Official { @@ -194,12 +196,12 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword }, nil } -// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete -// image. The auth configuration is serialized as a base64url encoded RFC4648, -// section 5) JSON string for sending through the X-Registry-Auth header. +// RetrieveAuthTokenFromImage retrieves an encoded auth token given a +// complete image reference. The auth configuration is serialized as a +// base64url encoded ([RFC 4648, Section 5]) JSON string for sending through +// the "X-Registry-Auth" header. // -// For details on base64url encoding, see: -// - RFC4648, section 5: https://tools.ietf.org/html/rfc4648#section-5 +// [RFC 4648, Section 5]: https://tools.ietf.org/html/rfc4648#section-5 func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (string, error) { // Retrieve encoded auth token from the image reference authConfig, err := resolveAuthConfigFromImage(cfg, image) @@ -233,8 +235,8 @@ func resolveAuthConfigFromImage(cfg *configfile.ConfigFile, image string) (regis // It is similar to [registry.GetAuthConfigKey], but does not require on // [registrytypes.IndexInfo] as intermediate. // -// [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker/registry#GetAuthConfigKey -// [registrytypes.IndexInfo]:https://pkg.go.dev/github.com/docker/docker/api/types/registry#IndexInfo +// [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#GetAuthConfigKey +// [registrytypes.IndexInfo]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/api/types/registry#IndexInfo func getAuthConfigKey(domainName string) string { if domainName == "docker.io" || domainName == "index.docker.io" { return authConfigKey From 942eade165a9ab1b6496dafa0bef4676135a1672 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 19 Aug 2025 23:21:58 +0200 Subject: [PATCH 060/193] cli/command: inline resolveAuthConfigFromImage Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 4286883b956cfafec25b421aa94ae16cefebe1c8) Signed-off-by: Sebastiaan van Stijn --- cli/command/registry.go | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/cli/command/registry.go b/cli/command/registry.go index b332b32e9023..dd6e74d4d46f 100644 --- a/cli/command/registry.go +++ b/cli/command/registry.go @@ -203,30 +203,21 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword // // [RFC 4648, Section 5]: https://tools.ietf.org/html/rfc4648#section-5 func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (string, error) { - // Retrieve encoded auth token from the image reference - authConfig, err := resolveAuthConfigFromImage(cfg, image) + registryRef, err := reference.ParseNormalizedNamed(image) if err != nil { return "", err } - encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig) + configKey := getAuthConfigKey(reference.Domain(registryRef)) + authConfig, err := cfg.GetAuthConfig(configKey) if err != nil { return "", err } - return encodedAuth, nil -} -// resolveAuthConfigFromImage retrieves that AuthConfig using the image string -func resolveAuthConfigFromImage(cfg *configfile.ConfigFile, image string) (registrytypes.AuthConfig, error) { - registryRef, err := reference.ParseNormalizedNamed(image) + encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig)) if err != nil { - return registrytypes.AuthConfig{}, err - } - configKey := getAuthConfigKey(reference.Domain(registryRef)) - a, err := cfg.GetAuthConfig(configKey) - if err != nil { - return registrytypes.AuthConfig{}, err + return "", err } - return registrytypes.AuthConfig(a), nil + return encodedAuth, nil } // getAuthConfigKey special-cases using the full index address of the official From 79c03dcaafe1a45c71b5c325f15117bfe973e1d4 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:11:57 +0200 Subject: [PATCH 061/193] Unexport the builder command and bake stub command This patch unexports the `builder` and `bake` stub command and it adds deprecation notices on the exported functions. It also registers the commands using the new `cli/internal/commands` package when the init function executes. Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 1b9d0762a5220d09d710db49ee0db88aa0e23f33) Signed-off-by: Sebastiaan van Stijn --- cli/command/builder/cmd.go | 20 ++++++++++++++++---- cli/command/commands/commands.go | 2 ++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/cli/command/builder/cmd.go b/cli/command/builder/cmd.go index ba2c069e8998..29a8422313bf 100644 --- a/cli/command/builder/cmd.go +++ b/cli/command/builder/cmd.go @@ -9,17 +9,23 @@ import ( ) // NewBuilderCommand returns a cobra command for `builder` subcommands -func NewBuilderCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewBuilderCommand(dockerCLI command.Cli) *cobra.Command { + return newBuilderCommand(dockerCLI) +} + +func newBuilderCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "builder", Short: "Manage builds", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), Annotations: map[string]string{"version": "1.31"}, } cmd.AddCommand( - NewPruneCommand(dockerCli), - image.NewBuildCommand(dockerCli), + NewPruneCommand(dockerCLI), + image.NewBuildCommand(dockerCLI), ) return cmd } @@ -28,7 +34,13 @@ func NewBuilderCommand(dockerCli command.Cli) *cobra.Command { // This command is a placeholder / stub that is dynamically replaced by an // alias for "docker buildx bake" if BuildKit is enabled (and the buildx plugin // installed). +// +// Deprecated: Do not import commands directly. They will be removed in a future release. func NewBakeStubCommand(dockerCLI command.Streams) *cobra.Command { + return newBakeStubCommand(dockerCLI) +} + +func newBakeStubCommand(dockerCLI command.Streams) *cobra.Command { return &cobra.Command{ Use: "bake [OPTIONS] [TARGET...]", Short: "Build from a file", diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index d3929293999d..c7a55ec16d4f 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -43,7 +43,9 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { system.NewInfoCommand(dockerCli), // management commands + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) builder.NewBakeStubCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) builder.NewBuilderCommand(dockerCli), checkpoint.NewCheckpointCommand(dockerCli), container.NewContainerCommand(dockerCli), From c94245da66247e805c0df88ba8299f84ce021139 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Mon, 18 Aug 2025 13:02:22 +0200 Subject: [PATCH 062/193] Unexport checkpoint command This patch unexports the `checkpoint` command. Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 3265cead1d170c216a8f6860dc5755c7cb52f08c) Signed-off-by: Sebastiaan van Stijn --- cli/command/checkpoint/cmd.go | 16 +++++++++++----- cli/command/commands/commands.go | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cli/command/checkpoint/cmd.go b/cli/command/checkpoint/cmd.go index 2a698e74e5e2..14154b2627ab 100644 --- a/cli/command/checkpoint/cmd.go +++ b/cli/command/checkpoint/cmd.go @@ -7,12 +7,18 @@ import ( ) // NewCheckpointCommand returns the `checkpoint` subcommand (only in experimental) -func NewCheckpointCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewCheckpointCommand(dockerCLI command.Cli) *cobra.Command { + return newCheckpointCommand(dockerCLI) +} + +func newCheckpointCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "checkpoint", Short: "Manage checkpoints", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), Annotations: map[string]string{ "experimental": "", "ostype": "linux", @@ -20,9 +26,9 @@ func NewCheckpointCommand(dockerCli command.Cli) *cobra.Command { }, } cmd.AddCommand( - newCreateCommand(dockerCli), - newListCommand(dockerCli), - newRemoveCommand(dockerCli), + newCreateCommand(dockerCLI), + newListCommand(dockerCLI), + newRemoveCommand(dockerCLI), ) return cmd } diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index c7a55ec16d4f..7bce95eafd4d 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -47,6 +47,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { builder.NewBakeStubCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) builder.NewBuilderCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) checkpoint.NewCheckpointCommand(dockerCli), container.NewContainerCommand(dockerCli), context.NewContextCommand(dockerCli), From 60caaa39e00fc848623a4a48b664b319061d3db9 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Mon, 18 Aug 2025 13:06:34 +0200 Subject: [PATCH 063/193] Unexport config command This patch unexports the `config` command. Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit cce29da061b87d67b331e10f719cc2fece359976) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 1 + cli/command/config/cmd.go | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index 7bce95eafd4d..8c6e93625f61 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -60,6 +60,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { volume.NewVolumeCommand(dockerCli), // orchestration (swarm) commands + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) config.NewConfigCommand(dockerCli), node.NewNodeCommand(dockerCli), secret.NewSecretCommand(dockerCli), diff --git a/cli/command/config/cmd.go b/cli/command/config/cmd.go index d83e33b2057b..ccfe6ae921a9 100644 --- a/cli/command/config/cmd.go +++ b/cli/command/config/cmd.go @@ -9,22 +9,28 @@ import ( ) // NewConfigCommand returns a cobra command for `config` subcommands -func NewConfigCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewConfigCommand(dockerCLI command.Cli) *cobra.Command { + return newConfigCommand(dockerCLI) +} + +func newConfigCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "config", Short: "Manage Swarm configs", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), Annotations: map[string]string{ "version": "1.30", "swarm": "manager", }, } cmd.AddCommand( - newConfigListCommand(dockerCli), - newConfigCreateCommand(dockerCli), - newConfigInspectCommand(dockerCli), - newConfigRemoveCommand(dockerCli), + newConfigListCommand(dockerCLI), + newConfigCreateCommand(dockerCLI), + newConfigInspectCommand(dockerCLI), + newConfigRemoveCommand(dockerCLI), ) return cmd } From f68a9a06fe5d23f1cbad82c276e5a1d4efdb1e2e Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Tue, 19 Aug 2025 11:12:19 +0200 Subject: [PATCH 064/193] Unexport container commands This patch deprecates exported container commands and moves the implementation details to an unexported function. Commands that are affected include: - container.NewRunCommand - container.NewExecCommand - container.NewPsCommand - container.NewContainerCommand - container.NewAttachCommand - container.NewCommitCommand - container.NewCopyCommand - container.NewCreateCommand - container.NewDiffCommand - container.NewExportCommand - container.NewKillCommand - container.NewLogsCommand - container.NewPauseCommand - container.NewPortCommand - container.NewRenameCommand - container.NewRestartCommand - container.NewRmCommand - container.NewStartCommand - container.NewStatsCommand - container.NewStopCommand - container.NewTopCommand - container.NewUnpauseCommand - container.NewUpdateCommand - container.NewWaitCommand - container.NewPruneCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 38595fecb6cd1cedecd1201d8a86b4443c467136) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 24 ++++++++++ cli/command/container/attach.go | 6 +++ cli/command/container/attach_test.go | 2 +- cli/command/container/cmd.go | 60 +++++++++++++----------- cli/command/container/commit.go | 12 +++-- cli/command/container/commit_test.go | 4 +- cli/command/container/completion_test.go | 2 +- cli/command/container/cp.go | 12 +++-- cli/command/container/create.go | 16 +++++-- cli/command/container/create_test.go | 8 ++-- cli/command/container/diff.go | 12 +++-- cli/command/container/diff_test.go | 4 +- cli/command/container/exec.go | 12 +++-- cli/command/container/exec_test.go | 2 +- cli/command/container/export.go | 12 +++-- cli/command/container/export_test.go | 4 +- cli/command/container/kill.go | 12 +++-- cli/command/container/kill_test.go | 4 +- cli/command/container/list.go | 8 +++- cli/command/container/logs.go | 12 +++-- cli/command/container/pause.go | 12 +++-- cli/command/container/pause_test.go | 4 +- cli/command/container/port.go | 6 +++ cli/command/container/port_test.go | 2 +- cli/command/container/prune.go | 14 ++++-- cli/command/container/prune_test.go | 2 +- cli/command/container/rename.go | 12 +++-- cli/command/container/rename_test.go | 4 +- cli/command/container/restart.go | 12 +++-- cli/command/container/restart_test.go | 2 +- cli/command/container/rm.go | 14 ++++-- cli/command/container/rm_test.go | 2 +- cli/command/container/run.go | 16 +++++-- cli/command/container/run_test.go | 12 ++--- cli/command/container/start.go | 6 +++ cli/command/container/stats.go | 6 +++ cli/command/container/stop.go | 12 +++-- cli/command/container/stop_test.go | 2 +- cli/command/container/top.go | 12 +++-- cli/command/container/unpause.go | 6 +++ cli/command/container/update.go | 12 +++-- cli/command/container/wait.go | 12 +++-- 42 files changed, 292 insertions(+), 118 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index 8c6e93625f61..c7518c073905 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -29,8 +29,11 @@ import ( func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { cmd.AddCommand( // commonly used shorthands + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) container.NewRunCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) container.NewExecCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) container.NewPsCommand(dockerCli), image.NewBuildCommand(dockerCli), image.NewPullCommand(dockerCli), @@ -49,6 +52,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { builder.NewBuilderCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) checkpoint.NewCheckpointCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) container.NewContainerCommand(dockerCli), context.NewContextCommand(dockerCli), image.NewImageCommand(dockerCli), @@ -69,25 +73,45 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { swarm.NewSwarmCommand(dockerCli), // legacy commands may be hidden + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewAttachCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewCommitCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewCopyCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewCreateCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewDiffCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewExportCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewKillCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewLogsCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewPauseCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewPortCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewRenameCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewRestartCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewRmCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewStartCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewStatsCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewStopCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewTopCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewUnpauseCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewUpdateCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewWaitCommand(dockerCli)), hide(image.NewHistoryCommand(dockerCli)), hide(image.NewImportCommand(dockerCli)), diff --git a/cli/command/container/attach.go b/cli/command/container/attach.go index f5ee5cf31115..c23bb0c049e5 100644 --- a/cli/command/container/attach.go +++ b/cli/command/container/attach.go @@ -41,7 +41,13 @@ func inspectContainerAndCheckState(ctx context.Context, apiClient client.APIClie } // NewAttachCommand creates a new cobra.Command for `docker attach` +// +// Deprecated: Do not import commands directly. They will be removed in a future release. func NewAttachCommand(dockerCLI command.Cli) *cobra.Command { + return newAttachCommand(dockerCLI) +} + +func newAttachCommand(dockerCLI command.Cli) *cobra.Command { var opts AttachOptions cmd := &cobra.Command{ diff --git a/cli/command/container/attach_test.go b/cli/command/container/attach_test.go index b4ae6e9f45a0..89ea9e9be93a 100644 --- a/cli/command/container/attach_test.go +++ b/cli/command/container/attach_test.go @@ -74,7 +74,7 @@ func TestNewAttachCommandErrors(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})) + cmd := newAttachCommand(test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) diff --git a/cli/command/container/cmd.go b/cli/command/container/cmd.go index 4ff00e74b591..7d0105373515 100644 --- a/cli/command/container/cmd.go +++ b/cli/command/container/cmd.go @@ -7,39 +7,45 @@ import ( ) // NewContainerCommand returns a cobra command for `container` subcommands -func NewContainerCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewContainerCommand(dockerCLI command.Cli) *cobra.Command { + return newContainerCommand(dockerCLI) +} + +func newContainerCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "container", Short: "Manage containers", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), } cmd.AddCommand( - NewAttachCommand(dockerCli), - NewCommitCommand(dockerCli), - NewCopyCommand(dockerCli), - NewCreateCommand(dockerCli), - NewDiffCommand(dockerCli), - NewExecCommand(dockerCli), - NewExportCommand(dockerCli), - NewKillCommand(dockerCli), - NewLogsCommand(dockerCli), - NewPauseCommand(dockerCli), - NewPortCommand(dockerCli), - NewRenameCommand(dockerCli), - NewRestartCommand(dockerCli), - newRemoveCommand(dockerCli), - NewRunCommand(dockerCli), - NewStartCommand(dockerCli), - NewStatsCommand(dockerCli), - NewStopCommand(dockerCli), - NewTopCommand(dockerCli), - NewUnpauseCommand(dockerCli), - NewUpdateCommand(dockerCli), - NewWaitCommand(dockerCli), - newListCommand(dockerCli), - newInspectCommand(dockerCli), - NewPruneCommand(dockerCli), + newAttachCommand(dockerCLI), + newCommitCommand(dockerCLI), + newCopyCommand(dockerCLI), + newCreateCommand(dockerCLI), + newDiffCommand(dockerCLI), + newExecCommand(dockerCLI), + newExportCommand(dockerCLI), + newKillCommand(dockerCLI), + newLogsCommand(dockerCLI), + newPauseCommand(dockerCLI), + newPortCommand(dockerCLI), + newRenameCommand(dockerCLI), + newRestartCommand(dockerCLI), + newRemoveCommand(dockerCLI), + newRunCommand(dockerCLI), + newStartCommand(dockerCLI), + newStatsCommand(dockerCLI), + newStopCommand(dockerCLI), + newTopCommand(dockerCLI), + newUnpauseCommand(dockerCLI), + newUpdateCommand(dockerCLI), + newWaitCommand(dockerCLI), + newListCommand(dockerCLI), + newInspectCommand(dockerCLI), + newPruneCommand(dockerCLI), ) return cmd } diff --git a/cli/command/container/commit.go b/cli/command/container/commit.go index 8c8c798f68bc..9dde0a183f02 100644 --- a/cli/command/container/commit.go +++ b/cli/command/container/commit.go @@ -23,7 +23,13 @@ type commitOptions struct { } // NewCommitCommand creates a new cobra.Command for `docker commit` -func NewCommitCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewCommitCommand(dockerCLI command.Cli) *cobra.Command { + return newCommitCommand(dockerCLI) +} + +func newCommitCommand(dockerCLI command.Cli) *cobra.Command { var options commitOptions cmd := &cobra.Command{ @@ -35,12 +41,12 @@ func NewCommitCommand(dockerCli command.Cli) *cobra.Command { if len(args) > 1 { options.reference = args[1] } - return runCommit(cmd.Context(), dockerCli, &options) + return runCommit(cmd.Context(), dockerCLI, &options) }, Annotations: map[string]string{ "aliases": "docker container commit, docker commit", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, false), + ValidArgsFunction: completion.ContainerNames(dockerCLI, false), } flags := cmd.Flags() diff --git a/cli/command/container/commit_test.go b/cli/command/container/commit_test.go index f1a62571fc7b..a2a08930f939 100644 --- a/cli/command/container/commit_test.go +++ b/cli/command/container/commit_test.go @@ -29,7 +29,7 @@ func TestRunCommit(t *testing.T) { }, }) - cmd := NewCommitCommand(cli) + cmd := newCommitCommand(cli) cmd.SetOut(io.Discard) cmd.SetArgs( []string{ @@ -60,7 +60,7 @@ func TestRunCommitClientError(t *testing.T) { }, }) - cmd := NewCommitCommand(cli) + cmd := newCommitCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs([]string{"container-id"}) diff --git a/cli/command/container/completion_test.go b/cli/command/container/completion_test.go index 0977fdc5192b..3721423d67b2 100644 --- a/cli/command/container/completion_test.go +++ b/cli/command/container/completion_test.go @@ -59,7 +59,7 @@ func TestCompletePid(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: tc.containerListFunc, }) - completions, directive := completePid(cli)(NewRunCommand(cli), nil, tc.toComplete) + completions, directive := completePid(cli)(newRunCommand(cli), nil, tc.toComplete) assert.Check(t, is.DeepEqual(completions, tc.expectedCompletions)) assert.Check(t, is.Equal(directive, tc.expectedDirective)) }) diff --git a/cli/command/container/cp.go b/cli/command/container/cp.go index 40b038458703..fb37897089a1 100644 --- a/cli/command/container/cp.go +++ b/cli/command/container/cp.go @@ -122,7 +122,13 @@ func copyProgress(ctx context.Context, dst io.Writer, header string, total *int6 } // NewCopyCommand creates a new `docker cp` command -func NewCopyCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewCopyCommand(dockerCLI command.Cli) *cobra.Command { + return newCopyCommand(dockerCLI) +} + +func newCopyCommand(dockerCLI command.Cli) *cobra.Command { var opts copyOptions cmd := &cobra.Command{ @@ -147,9 +153,9 @@ container source to stdout.`, opts.destination = args[1] if !cmd.Flag("quiet").Changed { // User did not specify "quiet" flag; suppress output if no terminal is attached - opts.quiet = !dockerCli.Out().IsTerminal() + opts.quiet = !dockerCLI.Out().IsTerminal() } - return runCopy(cmd.Context(), dockerCli, opts) + return runCopy(cmd.Context(), dockerCLI, opts) }, Annotations: map[string]string{ "aliases": "docker container cp, docker cp", diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 736ee568ea41..de635606b8d7 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -52,7 +52,13 @@ type createOptions struct { } // NewCreateCommand creates a new cobra.Command for `docker create` -func NewCreateCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewCreateCommand(dockerCLI command.Cli) *cobra.Command { + return newCreateCommand(dockerCLI) +} + +func newCreateCommand(dockerCLI command.Cli) *cobra.Command { var options createOptions var copts *containerOptions @@ -65,12 +71,12 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { if len(args) > 1 { copts.Args = args[1:] } - return runCreate(cmd.Context(), dockerCli, cmd.Flags(), &options, copts) + return runCreate(cmd.Context(), dockerCLI, cmd.Flags(), &options, copts) }, Annotations: map[string]string{ "aliases": "docker container create, docker create", }, - ValidArgsFunction: completion.ImageNames(dockerCli, -1), + ValidArgsFunction: completion.ImageNames(dockerCLI, -1), } flags := cmd.Flags() @@ -90,10 +96,10 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { addPlatformFlag(flags, &options.platform) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) - flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") + flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification") copts = addFlags(flags) - addCompletions(cmd, dockerCli) + addCompletions(cmd, dockerCLI) flags.VisitAll(func(flag *pflag.Flag) { // Set a default completion function if none was set. We don't look diff --git a/cli/command/container/create_test.go b/cli/command/container/create_test.go index fd94b624c822..cf68ace20f95 100644 --- a/cli/command/container/create_test.go +++ b/cli/command/container/create_test.go @@ -206,7 +206,7 @@ func TestCreateContainerValidateFlags(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - cmd := NewCreateCommand(test.NewFakeCli(&fakeClient{})) + cmd := newCreateCommand(test.NewFakeCli(&fakeClient{})) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) @@ -260,7 +260,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) { }, }, test.EnableContentTrust) fakeCLI.SetNotaryClient(tc.notaryFunc) - cmd := NewCreateCommand(fakeCLI) + cmd := newCreateCommand(fakeCLI) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) @@ -314,7 +314,7 @@ func TestNewCreateCommandWithWarnings(t *testing.T) { return container.CreateResponse{Warnings: tc.warnings}, nil }, }) - cmd := NewCreateCommand(fakeCLI) + cmd := newCreateCommand(fakeCLI) cmd.SetOut(io.Discard) cmd.SetArgs(tc.args) err := cmd.Execute() @@ -366,7 +366,7 @@ func TestCreateContainerWithProxyConfig(t *testing.T) { }, }, }) - cmd := NewCreateCommand(fakeCLI) + cmd := newCreateCommand(fakeCLI) cmd.SetOut(io.Discard) cmd.SetArgs([]string{"image:tag"}) err := cmd.Execute() diff --git a/cli/command/container/diff.go b/cli/command/container/diff.go index 93791fbd094e..26fdacc9c695 100644 --- a/cli/command/container/diff.go +++ b/cli/command/container/diff.go @@ -11,18 +11,24 @@ import ( ) // NewDiffCommand creates a new cobra.Command for `docker diff` -func NewDiffCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewDiffCommand(dockerCLI command.Cli) *cobra.Command { + return newDiffCommand(dockerCLI) +} + +func newDiffCommand(dockerCLI command.Cli) *cobra.Command { return &cobra.Command{ Use: "diff CONTAINER", Short: "Inspect changes to files or directories on a container's filesystem", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return runDiff(cmd.Context(), dockerCli, args[0]) + return runDiff(cmd.Context(), dockerCLI, args[0]) }, Annotations: map[string]string{ "aliases": "docker container diff, docker diff", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, false), + ValidArgsFunction: completion.ContainerNames(dockerCLI, false), } } diff --git a/cli/command/container/diff_test.go b/cli/command/container/diff_test.go index 685a95d125ba..4de209d5d186 100644 --- a/cli/command/container/diff_test.go +++ b/cli/command/container/diff_test.go @@ -36,7 +36,7 @@ func TestRunDiff(t *testing.T) { }, }) - cmd := NewDiffCommand(cli) + cmd := newDiffCommand(cli) cmd.SetOut(io.Discard) cmd.SetArgs([]string{"container-id"}) @@ -68,7 +68,7 @@ func TestRunDiffClientError(t *testing.T) { }, }) - cmd := NewDiffCommand(cli) + cmd := newDiffCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) diff --git a/cli/command/container/exec.go b/cli/command/container/exec.go index b491e24a1b8e..b4fdca397192 100644 --- a/cli/command/container/exec.go +++ b/cli/command/container/exec.go @@ -40,7 +40,13 @@ func NewExecOptions() ExecOptions { } // NewExecCommand creates a new cobra.Command for `docker exec` -func NewExecCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewExecCommand(dockerCLI command.Cli) *cobra.Command { + return newExecCommand(dockerCLI) +} + +func newExecCommand(dockerCLI command.Cli) *cobra.Command { options := NewExecOptions() cmd := &cobra.Command{ @@ -50,9 +56,9 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { containerIDorName := args[0] options.Command = args[1:] - return RunExec(cmd.Context(), dockerCli, containerIDorName, options) + return RunExec(cmd.Context(), dockerCLI, containerIDorName, options) }, - ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(ctr container.Summary) bool { + ValidArgsFunction: completion.ContainerNames(dockerCLI, false, func(ctr container.Summary) bool { return ctr.State != container.StatePaused }), Annotations: map[string]string{ diff --git a/cli/command/container/exec_test.go b/cli/command/container/exec_test.go index 9690d091c4d3..1e2cdb2759c9 100644 --- a/cli/command/container/exec_test.go +++ b/cli/command/container/exec_test.go @@ -263,7 +263,7 @@ func TestNewExecCommandErrors(t *testing.T) { } for _, tc := range testCases { fakeCLI := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc}) - cmd := NewExecCommand(fakeCLI) + cmd := newExecCommand(fakeCLI) cmd.SetOut(io.Discard) cmd.SetArgs(tc.args) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) diff --git a/cli/command/container/export.go b/cli/command/container/export.go index 990c2e66f8c8..ccb0ca29bbd5 100644 --- a/cli/command/container/export.go +++ b/cli/command/container/export.go @@ -18,7 +18,13 @@ type exportOptions struct { } // NewExportCommand creates a new `docker export` command -func NewExportCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewExportCommand(dockerCLI command.Cli) *cobra.Command { + return newExportCommand(dockerCLI) +} + +func newExportCommand(dockerCLI command.Cli) *cobra.Command { var opts exportOptions cmd := &cobra.Command{ @@ -27,12 +33,12 @@ func NewExportCommand(dockerCli command.Cli) *cobra.Command { Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.container = args[0] - return runExport(cmd.Context(), dockerCli, opts) + return runExport(cmd.Context(), dockerCLI, opts) }, Annotations: map[string]string{ "aliases": "docker container export, docker export", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, true), + ValidArgsFunction: completion.ContainerNames(dockerCLI, true), } flags := cmd.Flags() diff --git a/cli/command/container/export_test.go b/cli/command/container/export_test.go index 182713ab993a..9451eefcb7d7 100644 --- a/cli/command/container/export_test.go +++ b/cli/command/container/export_test.go @@ -19,7 +19,7 @@ func TestContainerExportOutputToFile(t *testing.T) { return io.NopCloser(strings.NewReader("bar")), nil }, }) - cmd := NewExportCommand(cli) + cmd := newExportCommand(cli) cmd.SetOut(io.Discard) cmd.SetArgs([]string{"-o", dir.Join("foo"), "container"}) assert.NilError(t, cmd.Execute()) @@ -37,7 +37,7 @@ func TestContainerExportOutputToIrregularFile(t *testing.T) { return io.NopCloser(strings.NewReader("foo")), nil }, }) - cmd := NewExportCommand(cli) + cmd := newExportCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs([]string{"-o", "/dev/random", "container"}) diff --git a/cli/command/container/kill.go b/cli/command/container/kill.go index 3d5c59941831..1d8521563050 100644 --- a/cli/command/container/kill.go +++ b/cli/command/container/kill.go @@ -18,7 +18,13 @@ type killOptions struct { } // NewKillCommand creates a new cobra.Command for `docker kill` -func NewKillCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewKillCommand(dockerCLI command.Cli) *cobra.Command { + return newKillCommand(dockerCLI) +} + +func newKillCommand(dockerCLI command.Cli) *cobra.Command { var opts killOptions cmd := &cobra.Command{ @@ -27,12 +33,12 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command { Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.containers = args - return runKill(cmd.Context(), dockerCli, &opts) + return runKill(cmd.Context(), dockerCLI, &opts) }, Annotations: map[string]string{ "aliases": "docker container kill, docker kill", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, false), + ValidArgsFunction: completion.ContainerNames(dockerCLI, false), } flags := cmd.Flags() diff --git a/cli/command/container/kill_test.go b/cli/command/container/kill_test.go index 7bd1f10b9272..c5268dd4bc66 100644 --- a/cli/command/container/kill_test.go +++ b/cli/command/container/kill_test.go @@ -24,7 +24,7 @@ func TestRunKill(t *testing.T) { }, }) - cmd := NewKillCommand(cli) + cmd := newKillCommand(cli) cmd.SetOut(io.Discard) cmd.SetArgs([]string{ @@ -56,7 +56,7 @@ func TestRunKillClientError(t *testing.T) { }, }) - cmd := NewKillCommand(cli) + cmd := newKillCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) diff --git a/cli/command/container/list.go b/cli/command/container/list.go index 4523fe51d216..5a30291ff224 100644 --- a/cli/command/container/list.go +++ b/cli/command/container/list.go @@ -29,7 +29,13 @@ type psOptions struct { } // NewPsCommand creates a new cobra.Command for `docker ps` +// +// Deprecated: Do not import commands directly. They will be removed in a future release. func NewPsCommand(dockerCLI command.Cli) *cobra.Command { + return newPsCommand(dockerCLI) +} + +func newPsCommand(dockerCLI command.Cli) *cobra.Command { options := psOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ @@ -62,7 +68,7 @@ func NewPsCommand(dockerCLI command.Cli) *cobra.Command { } func newListCommand(dockerCLI command.Cli) *cobra.Command { - cmd := *NewPsCommand(dockerCLI) + cmd := *newPsCommand(dockerCLI) cmd.Aliases = []string{"ps", "list"} cmd.Use = "ls [OPTIONS]" return &cmd diff --git a/cli/command/container/logs.go b/cli/command/container/logs.go index 3d536f721b73..62b6f7489785 100644 --- a/cli/command/container/logs.go +++ b/cli/command/container/logs.go @@ -24,7 +24,13 @@ type logsOptions struct { } // NewLogsCommand creates a new cobra.Command for `docker logs` -func NewLogsCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewLogsCommand(dockerCLI command.Cli) *cobra.Command { + return newLogsCommand(dockerCLI) +} + +func newLogsCommand(dockerCLI command.Cli) *cobra.Command { var opts logsOptions cmd := &cobra.Command{ @@ -33,12 +39,12 @@ func NewLogsCommand(dockerCli command.Cli) *cobra.Command { Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.container = args[0] - return runLogs(cmd.Context(), dockerCli, &opts) + return runLogs(cmd.Context(), dockerCLI, &opts) }, Annotations: map[string]string{ "aliases": "docker container logs, docker logs", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, true), + ValidArgsFunction: completion.ContainerNames(dockerCLI, true), } flags := cmd.Flags() diff --git a/cli/command/container/pause.go b/cli/command/container/pause.go index 78dc6fe37d8d..d5c75c861143 100644 --- a/cli/command/container/pause.go +++ b/cli/command/container/pause.go @@ -17,7 +17,13 @@ type pauseOptions struct { } // NewPauseCommand creates a new cobra.Command for `docker pause` -func NewPauseCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewPauseCommand(dockerCLI command.Cli) *cobra.Command { + return newPauseCommand(dockerCLI) +} + +func newPauseCommand(dockerCLI command.Cli) *cobra.Command { var opts pauseOptions return &cobra.Command{ @@ -26,12 +32,12 @@ func NewPauseCommand(dockerCli command.Cli) *cobra.Command { Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.containers = args - return runPause(cmd.Context(), dockerCli, &opts) + return runPause(cmd.Context(), dockerCLI, &opts) }, Annotations: map[string]string{ "aliases": "docker container pause, docker pause", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(ctr container.Summary) bool { + ValidArgsFunction: completion.ContainerNames(dockerCLI, false, func(ctr container.Summary) bool { return ctr.State != container.StatePaused }), } diff --git a/cli/command/container/pause_test.go b/cli/command/container/pause_test.go index a359797a94cf..04e4f40672b4 100644 --- a/cli/command/container/pause_test.go +++ b/cli/command/container/pause_test.go @@ -21,7 +21,7 @@ func TestRunPause(t *testing.T) { }, ) - cmd := NewPauseCommand(cli) + cmd := newPauseCommand(cli) cmd.SetOut(io.Discard) cmd.SetArgs([]string{"container-id-1", "container-id-2"}) @@ -47,7 +47,7 @@ func TestRunPauseClientError(t *testing.T) { }, ) - cmd := NewPauseCommand(cli) + cmd := newPauseCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs([]string{"container-id-1", "container-id-2"}) diff --git a/cli/command/container/port.go b/cli/command/container/port.go index 3ed950f37582..a67e939c8920 100644 --- a/cli/command/container/port.go +++ b/cli/command/container/port.go @@ -24,7 +24,13 @@ type portOptions struct { } // NewPortCommand creates a new cobra.Command for `docker port` +// +// Deprecated: Do not import commands directly. They will be removed in a future release. func NewPortCommand(dockerCli command.Cli) *cobra.Command { + return newPortCommand(dockerCli) +} + +func newPortCommand(dockerCli command.Cli) *cobra.Command { var opts portOptions cmd := &cobra.Command{ diff --git a/cli/command/container/port_test.go b/cli/command/container/port_test.go index 5ed907dcb2d7..a904135ad03e 100644 --- a/cli/command/container/port_test.go +++ b/cli/command/container/port_test.go @@ -66,7 +66,7 @@ func TestNewPortCommandOutput(t *testing.T) { return ci, nil }, }) - cmd := NewPortCommand(cli) + cmd := newPortCommand(cli) cmd.SetErr(io.Discard) cmd.SetArgs([]string{"some_container", tc.port}) err := cmd.Execute() diff --git a/cli/command/container/prune.go b/cli/command/container/prune.go index d75338718e36..5baf040b79fa 100644 --- a/cli/command/container/prune.go +++ b/cli/command/container/prune.go @@ -20,7 +20,13 @@ type pruneOptions struct { } // NewPruneCommand returns a new cobra prune command for containers -func NewPruneCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewPruneCommand(dockerCLI command.Cli) *cobra.Command { + return newPruneCommand(dockerCLI) +} + +func newPruneCommand(dockerCLI command.Cli) *cobra.Command { options := pruneOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ @@ -28,14 +34,14 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { Short: "Remove all stopped containers", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCli, options) + spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCLI, options) if err != nil { return err } if output != "" { - fmt.Fprintln(dockerCli.Out(), output) + fmt.Fprintln(dockerCLI.Out(), output) } - fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) + fmt.Fprintln(dockerCLI.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) return nil }, Annotations: map[string]string{"version": "1.25"}, diff --git a/cli/command/container/prune_test.go b/cli/command/container/prune_test.go index 6700235c3ddb..bfc321d6117d 100644 --- a/cli/command/container/prune_test.go +++ b/cli/command/container/prune_test.go @@ -20,7 +20,7 @@ func TestContainerPrunePromptTermination(t *testing.T) { return container.PruneReport{}, errors.New("fakeClient containerPruneFunc should not be called") }, }) - cmd := NewPruneCommand(cli) + cmd := newPruneCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) diff --git a/cli/command/container/rename.go b/cli/command/container/rename.go index a871e38d1ef9..2f028e872e1f 100644 --- a/cli/command/container/rename.go +++ b/cli/command/container/rename.go @@ -18,7 +18,13 @@ type renameOptions struct { } // NewRenameCommand creates a new cobra.Command for `docker rename` -func NewRenameCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewRenameCommand(dockerCLI command.Cli) *cobra.Command { + return newRenameCommand(dockerCLI) +} + +func newRenameCommand(dockerCLI command.Cli) *cobra.Command { var opts renameOptions cmd := &cobra.Command{ @@ -28,12 +34,12 @@ func NewRenameCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { opts.oldName = args[0] opts.newName = args[1] - return runRename(cmd.Context(), dockerCli, &opts) + return runRename(cmd.Context(), dockerCLI, &opts) }, Annotations: map[string]string{ "aliases": "docker container rename, docker rename", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, true), + ValidArgsFunction: completion.ContainerNames(dockerCLI, true), } return cmd } diff --git a/cli/command/container/rename_test.go b/cli/command/container/rename_test.go index 9a2cae4b3cac..2e14e771e534 100644 --- a/cli/command/container/rename_test.go +++ b/cli/command/container/rename_test.go @@ -43,7 +43,7 @@ func TestRunRename(t *testing.T) { }, }) - cmd := NewRenameCommand(cli) + cmd := newRenameCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs([]string{tc.oldName, tc.newName}) @@ -66,7 +66,7 @@ func TestRunRenameClientError(t *testing.T) { }, }) - cmd := NewRenameCommand(cli) + cmd := newRenameCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs([]string{"oldName", "newName"}) diff --git a/cli/command/container/restart.go b/cli/command/container/restart.go index 379b6a12eba5..1e7954e261de 100644 --- a/cli/command/container/restart.go +++ b/cli/command/container/restart.go @@ -21,7 +21,13 @@ type restartOptions struct { } // NewRestartCommand creates a new cobra.Command for `docker restart` -func NewRestartCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewRestartCommand(dockerCLI command.Cli) *cobra.Command { + return newRestartCommand(dockerCLI) +} + +func newRestartCommand(dockerCLI command.Cli) *cobra.Command { var opts restartOptions cmd := &cobra.Command{ @@ -34,12 +40,12 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command { } opts.containers = args opts.timeoutChanged = cmd.Flags().Changed("timeout") || cmd.Flags().Changed("time") - return runRestart(cmd.Context(), dockerCli, &opts) + return runRestart(cmd.Context(), dockerCLI, &opts) }, Annotations: map[string]string{ "aliases": "docker container restart, docker restart", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, true), + ValidArgsFunction: completion.ContainerNames(dockerCLI, true), } flags := cmd.Flags() diff --git a/cli/command/container/restart_test.go b/cli/command/container/restart_test.go index f7986a87514f..c50134bf1cfb 100644 --- a/cli/command/container/restart_test.go +++ b/cli/command/container/restart_test.go @@ -76,7 +76,7 @@ func TestRestart(t *testing.T) { }, Version: "1.36", }) - cmd := NewRestartCommand(cli) + cmd := newRestartCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) diff --git a/cli/command/container/rm.go b/cli/command/container/rm.go index f2b360657d01..cf3d6a66a881 100644 --- a/cli/command/container/rm.go +++ b/cli/command/container/rm.go @@ -23,7 +23,13 @@ type rmOptions struct { } // NewRmCommand creates a new cobra.Command for `docker rm` -func NewRmCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewRmCommand(dockerCLI command.Cli) *cobra.Command { + return newRmCommand(dockerCLI) +} + +func newRmCommand(dockerCLI command.Cli) *cobra.Command { var opts rmOptions cmd := &cobra.Command{ @@ -32,12 +38,12 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command { Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.containers = args - return runRm(cmd.Context(), dockerCli, &opts) + return runRm(cmd.Context(), dockerCLI, &opts) }, Annotations: map[string]string{ "aliases": "docker container rm, docker container remove, docker rm", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, true, func(ctr container.Summary) bool { + ValidArgsFunction: completion.ContainerNames(dockerCLI, true, func(ctr container.Summary) bool { return opts.force || ctr.State == container.StateExited || ctr.State == container.StateCreated }), } @@ -53,7 +59,7 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command { // top-level "docker rm", it also adds a "remove" alias to support // "docker container remove" in addition to "docker container rm". func newRemoveCommand(dockerCli command.Cli) *cobra.Command { - cmd := *NewRmCommand(dockerCli) + cmd := *newRmCommand(dockerCli) cmd.Aliases = []string{"rm", "remove"} return &cmd } diff --git a/cli/command/container/rm_test.go b/cli/command/container/rm_test.go index e9850a9b93b8..1bbaaff213c0 100644 --- a/cli/command/container/rm_test.go +++ b/cli/command/container/rm_test.go @@ -41,7 +41,7 @@ func TestRemoveForce(t *testing.T) { }, Version: "1.36", }) - cmd := NewRmCommand(cli) + cmd := newRmCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) diff --git a/cli/command/container/run.go b/cli/command/container/run.go index 6c5748a67ae1..2674e5b11c4b 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -28,7 +28,13 @@ type runOptions struct { } // NewRunCommand create a new `docker run` command -func NewRunCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewRunCommand(dockerCLI command.Cli) *cobra.Command { + return newRunCommand(dockerCLI) +} + +func newRunCommand(dockerCLI command.Cli) *cobra.Command { var options runOptions var copts *containerOptions @@ -41,9 +47,9 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { if len(args) > 1 { copts.Args = args[1:] } - return runRun(cmd.Context(), dockerCli, cmd.Flags(), &options, copts) + return runRun(cmd.Context(), dockerCLI, cmd.Flags(), &options, copts) }, - ValidArgsFunction: completion.ImageNames(dockerCli, 1), + ValidArgsFunction: completion.ImageNames(dockerCLI, 1), Annotations: map[string]string{ "category-top": "1", "aliases": "docker container run, docker run", @@ -68,11 +74,11 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { // TODO(thaJeztah): consider adding platform as "image create option" on containerOptions addPlatformFlag(flags, &options.platform) - flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") + flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification") copts = addFlags(flags) _ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys) - addCompletions(cmd, dockerCli) + addCompletions(cmd, dockerCLI) flags.VisitAll(func(flag *pflag.Flag) { // Set a default completion function if none was set. We don't look diff --git a/cli/command/container/run_test.go b/cli/command/container/run_test.go index 17a931c8c00a..f75fe7ebefde 100644 --- a/cli/command/container/run_test.go +++ b/cli/command/container/run_test.go @@ -39,7 +39,7 @@ func TestRunValidateFlags(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - cmd := NewRunCommand(test.NewFakeCli(&fakeClient{})) + cmd := newRunCommand(test.NewFakeCli(&fakeClient{})) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) @@ -63,7 +63,7 @@ func TestRunLabel(t *testing.T) { }, Version: "1.36", }) - cmd := NewRunCommand(fakeCLI) + cmd := newRunCommand(fakeCLI) cmd.SetArgs([]string{"--detach=true", "--label", "foo", "busybox"}) assert.NilError(t, cmd.Execute()) } @@ -110,7 +110,7 @@ func TestRunAttach(t *testing.T) { fc.SetIn(streams.NewIn(tty)) }) - cmd := NewRunCommand(fakeCLI) + cmd := newRunCommand(fakeCLI) cmd.SetArgs([]string{"-it", "busybox"}) cmd.SilenceUsage = true cmdErrC := make(chan error, 1) @@ -187,7 +187,7 @@ func TestRunAttachTermination(t *testing.T) { fc.SetIn(streams.NewIn(tty)) }) - cmd := NewRunCommand(fakeCLI) + cmd := newRunCommand(fakeCLI) cmd.SetArgs([]string{"-it", "busybox"}) cmd.SilenceUsage = true cmdErrC := make(chan error, 1) @@ -265,7 +265,7 @@ func TestRunPullTermination(t *testing.T) { Version: "1.30", }) - cmd := NewRunCommand(fakeCLI) + cmd := newRunCommand(fakeCLI) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs([]string{"--pull", "always", "foobar:latest"}) @@ -334,7 +334,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) { }, }, test.EnableContentTrust) fakeCLI.SetNotaryClient(tc.notaryFunc) - cmd := NewRunCommand(fakeCLI) + cmd := newRunCommand(fakeCLI) cmd.SetArgs(tc.args) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) diff --git a/cli/command/container/start.go b/cli/command/container/start.go index a71bdc8fde65..3daf586e0a8b 100644 --- a/cli/command/container/start.go +++ b/cli/command/container/start.go @@ -28,7 +28,13 @@ type StartOptions struct { } // NewStartCommand creates a new cobra.Command for `docker start` +// +// Deprecated: Do not import commands directly. They will be removed in a future release. func NewStartCommand(dockerCli command.Cli) *cobra.Command { + return newStartCommand(dockerCli) +} + +func newStartCommand(dockerCli command.Cli) *cobra.Command { var opts StartOptions cmd := &cobra.Command{ diff --git a/cli/command/container/stats.go b/cli/command/container/stats.go index 5538e61dce20..7e3a94625296 100644 --- a/cli/command/container/stats.go +++ b/cli/command/container/stats.go @@ -64,7 +64,13 @@ type StatsOptions struct { } // NewStatsCommand creates a new [cobra.Command] for "docker stats". +// +// Deprecated: Do not import commands directly. They will be removed in a future release. func NewStatsCommand(dockerCLI command.Cli) *cobra.Command { + return newStatsCommand(dockerCLI) +} + +func newStatsCommand(dockerCLI command.Cli) *cobra.Command { options := StatsOptions{} cmd := &cobra.Command{ diff --git a/cli/command/container/stop.go b/cli/command/container/stop.go index c6b331e964fd..e84b9e959c3c 100644 --- a/cli/command/container/stop.go +++ b/cli/command/container/stop.go @@ -21,7 +21,13 @@ type stopOptions struct { } // NewStopCommand creates a new cobra.Command for `docker stop` -func NewStopCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewStopCommand(dockerCLI command.Cli) *cobra.Command { + return newStopCommand(dockerCLI) +} + +func newStopCommand(dockerCLI command.Cli) *cobra.Command { var opts stopOptions cmd := &cobra.Command{ @@ -34,12 +40,12 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command { } opts.containers = args opts.timeoutChanged = cmd.Flags().Changed("timeout") || cmd.Flags().Changed("time") - return runStop(cmd.Context(), dockerCli, &opts) + return runStop(cmd.Context(), dockerCLI, &opts) }, Annotations: map[string]string{ "aliases": "docker container stop, docker stop", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, false), + ValidArgsFunction: completion.ContainerNames(dockerCLI, false), } flags := cmd.Flags() diff --git a/cli/command/container/stop_test.go b/cli/command/container/stop_test.go index fc2b88c7acc4..424227f9b25a 100644 --- a/cli/command/container/stop_test.go +++ b/cli/command/container/stop_test.go @@ -77,7 +77,7 @@ func TestStop(t *testing.T) { }, Version: "1.36", }) - cmd := NewStopCommand(cli) + cmd := newStopCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) diff --git a/cli/command/container/top.go b/cli/command/container/top.go index 411fcbbf79cd..d6a5970e358f 100644 --- a/cli/command/container/top.go +++ b/cli/command/container/top.go @@ -19,7 +19,13 @@ type topOptions struct { } // NewTopCommand creates a new cobra.Command for `docker top` -func NewTopCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewTopCommand(dockerCLI command.Cli) *cobra.Command { + return newTopCommand(dockerCLI) +} + +func newTopCommand(dockerCLI command.Cli) *cobra.Command { var opts topOptions cmd := &cobra.Command{ @@ -29,12 +35,12 @@ func NewTopCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { opts.container = args[0] opts.args = args[1:] - return runTop(cmd.Context(), dockerCli, &opts) + return runTop(cmd.Context(), dockerCLI, &opts) }, Annotations: map[string]string{ "aliases": "docker container top, docker top", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, false), + ValidArgsFunction: completion.ContainerNames(dockerCLI, false), } flags := cmd.Flags() diff --git a/cli/command/container/unpause.go b/cli/command/container/unpause.go index fd5a516cace4..f5a002f3e279 100644 --- a/cli/command/container/unpause.go +++ b/cli/command/container/unpause.go @@ -17,7 +17,13 @@ type unpauseOptions struct { } // NewUnpauseCommand creates a new cobra.Command for `docker unpause` +// +// Deprecated: Do not import commands directly. They will be removed in a future release. func NewUnpauseCommand(dockerCli command.Cli) *cobra.Command { + return newUnpauseCommand(dockerCli) +} + +func newUnpauseCommand(dockerCli command.Cli) *cobra.Command { var opts unpauseOptions cmd := &cobra.Command{ diff --git a/cli/command/container/update.go b/cli/command/container/update.go index 275ec5f64634..0f960477cb42 100644 --- a/cli/command/container/update.go +++ b/cli/command/container/update.go @@ -37,7 +37,13 @@ type updateOptions struct { } // NewUpdateCommand creates a new cobra.Command for `docker update` -func NewUpdateCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewUpdateCommand(dockerCLI command.Cli) *cobra.Command { + return newUpdateCommand(dockerCLI) +} + +func newUpdateCommand(dockerCLI command.Cli) *cobra.Command { var options updateOptions cmd := &cobra.Command{ @@ -47,12 +53,12 @@ func NewUpdateCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { options.containers = args options.nFlag = cmd.Flags().NFlag() - return runUpdate(cmd.Context(), dockerCli, &options) + return runUpdate(cmd.Context(), dockerCLI, &options) }, Annotations: map[string]string{ "aliases": "docker container update, docker update", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, true), + ValidArgsFunction: completion.ContainerNames(dockerCLI, true), } flags := cmd.Flags() diff --git a/cli/command/container/wait.go b/cli/command/container/wait.go index 89f2ba17acb6..95c389667730 100644 --- a/cli/command/container/wait.go +++ b/cli/command/container/wait.go @@ -16,7 +16,13 @@ type waitOptions struct { } // NewWaitCommand creates a new cobra.Command for `docker wait` -func NewWaitCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewWaitCommand(dockerCLI command.Cli) *cobra.Command { + return newWaitCommand(dockerCLI) +} + +func newWaitCommand(dockerCLI command.Cli) *cobra.Command { var opts waitOptions cmd := &cobra.Command{ @@ -25,12 +31,12 @@ func NewWaitCommand(dockerCli command.Cli) *cobra.Command { Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.containers = args - return runWait(cmd.Context(), dockerCli, &opts) + return runWait(cmd.Context(), dockerCLI, &opts) }, Annotations: map[string]string{ "aliases": "docker container wait, docker wait", }, - ValidArgsFunction: completion.ContainerNames(dockerCli, false), + ValidArgsFunction: completion.ContainerNames(dockerCLI, false), } return cmd From 9d258edf27cc0d482521dac02c7aa0fbf90d4229 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Tue, 19 Aug 2025 14:09:53 +0200 Subject: [PATCH 065/193] Unexport image commands This patch deprecates exported image commands and moves the implementation details to an unexported function. Commands that are affected include: - image.NewBuildCommand - image.NewPullCommand - image.NewPushCommand - image.NewImagesCommand - image.NewImageCommand - image.NewHistoryCommand - image.NewImportCommand - image.NewLoadCommand - image.NewRemoveCommand - image.NewSaveCommand - image.NewTagCommand - image.NewPruneCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit e66a1456d312cb4e2c09acdc3475c45ce36d6865) Signed-off-by: Sebastiaan van Stijn --- cli/command/builder/cmd.go | 2 ++ cli/command/commands/commands.go | 11 +++++++++++ cli/command/image/build.go | 9 ++++++++- cli/command/image/build_test.go | 4 ++-- cli/command/image/cmd.go | 29 ++++++++++++++++++----------- cli/command/image/history.go | 13 ++++++++++--- cli/command/image/history_test.go | 4 ++-- cli/command/image/import.go | 11 +++++++++-- cli/command/image/import_test.go | 6 +++--- cli/command/image/list.go | 9 ++++++++- cli/command/image/list_test.go | 6 +++--- cli/command/image/load.go | 11 +++++++++-- cli/command/image/load_test.go | 6 +++--- cli/command/image/prune.go | 15 +++++++++++---- cli/command/image/pull.go | 13 ++++++++++--- cli/command/image/pull_test.go | 6 +++--- cli/command/image/push.go | 15 +++++++++++---- cli/command/image/push_test.go | 4 ++-- cli/command/image/remove.go | 12 ++++++++++-- cli/command/image/remove_test.go | 6 +++--- cli/command/image/save.go | 13 ++++++++++--- cli/command/image/save_test.go | 4 ++-- cli/command/image/tag.go | 9 ++++++++- cli/command/image/tag_test.go | 4 ++-- 24 files changed, 160 insertions(+), 62 deletions(-) diff --git a/cli/command/builder/cmd.go b/cli/command/builder/cmd.go index 29a8422313bf..44d6496397ae 100644 --- a/cli/command/builder/cmd.go +++ b/cli/command/builder/cmd.go @@ -25,6 +25,8 @@ func newBuilderCommand(dockerCLI command.Cli) *cobra.Command { } cmd.AddCommand( NewPruneCommand(dockerCLI), + // we should have a mechanism for registering sub-commands in the cli/internal/commands.Register function. + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) image.NewBuildCommand(dockerCLI), ) return cmd diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index c7518c073905..a735e36d8ccd 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -35,9 +35,13 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { container.NewExecCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) container.NewPsCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) image.NewBuildCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) image.NewPullCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) image.NewPushCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) image.NewImagesCommand(dockerCli), registry.NewLoginCommand(dockerCli), registry.NewLogoutCommand(dockerCli), @@ -55,6 +59,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) container.NewContainerCommand(dockerCli), context.NewContextCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) image.NewImageCommand(dockerCli), manifest.NewManifestCommand(dockerCli), network.NewNetworkCommand(dockerCli), @@ -113,11 +118,17 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { hide(container.NewUpdateCommand(dockerCli)), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(container.NewWaitCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(image.NewHistoryCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(image.NewImportCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(image.NewLoadCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(image.NewRemoveCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(image.NewSaveCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(image.NewTagCommand(dockerCli)), hide(system.NewEventsCommand(dockerCli)), hide(system.NewInspectCommand(dockerCli)), diff --git a/cli/command/image/build.go b/cli/command/image/build.go index 15448e7b4927..f7f0493d5d6f 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -87,7 +87,14 @@ func newBuildOptions() buildOptions { } // NewBuildCommand creates a new `docker build` command -func NewBuildCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewBuildCommand(dockerCLI command.Cli) *cobra.Command { + return newBuildCommand(dockerCLI) +} + +// newBuildCommand creates a new `docker build` command +func newBuildCommand(dockerCli command.Cli) *cobra.Command { options := newBuildOptions() cmd := &cobra.Command{ diff --git a/cli/command/image/build_test.go b/cli/command/image/build_test.go index 22105e47c024..f1bfacf24bdc 100644 --- a/cli/command/image/build_test.go +++ b/cli/command/image/build_test.go @@ -123,7 +123,7 @@ COPY data /data // to support testing (ex: docker/cli#294) func TestRunBuildFromGitHubSpecialCase(t *testing.T) { t.Setenv("DOCKER_BUILDKIT", "0") - cmd := NewBuildCommand(test.NewFakeCli(&fakeClient{})) + cmd := newBuildCommand(test.NewFakeCli(&fakeClient{})) // Clone a small repo that exists so git doesn't prompt for credentials cmd.SetArgs([]string{"github.com/docker/for-win"}) cmd.SetOut(io.Discard) @@ -146,7 +146,7 @@ func TestRunBuildFromLocalGitHubDir(t *testing.T) { assert.NilError(t, err) client := test.NewFakeCli(&fakeClient{}) - cmd := NewBuildCommand(client) + cmd := newBuildCommand(client) cmd.SetArgs([]string{buildDir}) cmd.SetOut(io.Discard) err = cmd.Execute() diff --git a/cli/command/image/cmd.go b/cli/command/image/cmd.go index c035da17f05c..1532a7e21bc3 100644 --- a/cli/command/image/cmd.go +++ b/cli/command/image/cmd.go @@ -7,7 +7,14 @@ import ( ) // NewImageCommand returns a cobra command for `image` subcommands -func NewImageCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewImageCommand(dockerCLI command.Cli) *cobra.Command { + return newImageCommand(dockerCLI) +} + +// newImageCommand returns a cobra command for `image` subcommands +func newImageCommand(dockerCli command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "image", Short: "Manage images", @@ -15,18 +22,18 @@ func NewImageCommand(dockerCli command.Cli) *cobra.Command { RunE: command.ShowHelp(dockerCli.Err()), } cmd.AddCommand( - NewBuildCommand(dockerCli), - NewHistoryCommand(dockerCli), - NewImportCommand(dockerCli), - NewLoadCommand(dockerCli), - NewPullCommand(dockerCli), - NewPushCommand(dockerCli), - NewSaveCommand(dockerCli), - NewTagCommand(dockerCli), + newBuildCommand(dockerCli), + newHistoryCommand(dockerCli), + newImportCommand(dockerCli), + newLoadCommand(dockerCli), + newPullCommand(dockerCli), + newPushCommand(dockerCli), + newSaveCommand(dockerCli), + newTagCommand(dockerCli), newListCommand(dockerCli), - newRemoveCommand(dockerCli), + newImageRemoveCommand(dockerCli), newInspectCommand(dockerCli), - NewPruneCommand(dockerCli), + newPruneCommand(dockerCli), ) return cmd } diff --git a/cli/command/image/history.go b/cli/command/image/history.go index 075b3d84db31..16396b89796c 100644 --- a/cli/command/image/history.go +++ b/cli/command/image/history.go @@ -25,7 +25,14 @@ type historyOptions struct { } // NewHistoryCommand creates a new `docker history` command -func NewHistoryCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewHistoryCommand(dockerCLI command.Cli) *cobra.Command { + return newHistoryCommand(dockerCLI) +} + +// newHistoryCommand creates a new `docker history` command +func newHistoryCommand(dockerCLI command.Cli) *cobra.Command { var opts historyOptions cmd := &cobra.Command{ @@ -34,9 +41,9 @@ func NewHistoryCommand(dockerCli command.Cli) *cobra.Command { Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.image = args[0] - return runHistory(cmd.Context(), dockerCli, opts) + return runHistory(cmd.Context(), dockerCLI, opts) }, - ValidArgsFunction: completion.ImageNames(dockerCli, 1), + ValidArgsFunction: completion.ImageNames(dockerCLI, 1), Annotations: map[string]string{ "aliases": "docker image history, docker history", }, diff --git a/cli/command/image/history_test.go b/cli/command/image/history_test.go index 7ecc054d0a1b..a5dc0cb86ccf 100644 --- a/cli/command/image/history_test.go +++ b/cli/command/image/history_test.go @@ -42,7 +42,7 @@ func TestNewHistoryCommandErrors(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc})) + cmd := newHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc})) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) @@ -114,7 +114,7 @@ func TestNewHistoryCommandSuccess(t *testing.T) { // printed in the current timezone t.Setenv("TZ", "UTC") cli := test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}) - cmd := NewHistoryCommand(cli) + cmd := newHistoryCommand(cli) cmd.SetOut(io.Discard) cmd.SetArgs(tc.args) err := cmd.Execute() diff --git a/cli/command/image/import.go b/cli/command/image/import.go index f0da5c6a4752..d94e9cf72a03 100644 --- a/cli/command/image/import.go +++ b/cli/command/image/import.go @@ -23,7 +23,14 @@ type importOptions struct { } // NewImportCommand creates a new `docker import` command -func NewImportCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewImportCommand(dockerCLI command.Cli) *cobra.Command { + return newImportCommand(dockerCLI) +} + +// newImportCommand creates a new `docker import` command +func newImportCommand(dockerCLI command.Cli) *cobra.Command { var options importOptions cmd := &cobra.Command{ @@ -35,7 +42,7 @@ func NewImportCommand(dockerCli command.Cli) *cobra.Command { if len(args) > 1 { options.reference = args[1] } - return runImport(cmd.Context(), dockerCli, options) + return runImport(cmd.Context(), dockerCLI, options) }, Annotations: map[string]string{ "aliases": "docker image import, docker import", diff --git a/cli/command/image/import_test.go b/cli/command/image/import_test.go index d6d69948df1f..05675a977a53 100644 --- a/cli/command/image/import_test.go +++ b/cli/command/image/import_test.go @@ -34,7 +34,7 @@ func TestNewImportCommandErrors(t *testing.T) { }, } for _, tc := range testCases { - cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc})) + cmd := newImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc})) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) @@ -43,7 +43,7 @@ func TestNewImportCommandErrors(t *testing.T) { } func TestNewImportCommandInvalidFile(t *testing.T) { - cmd := NewImportCommand(test.NewFakeCli(&fakeClient{})) + cmd := newImportCommand(test.NewFakeCli(&fakeClient{})) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs([]string{"testdata/import-command-success.unexistent-file"}) @@ -99,7 +99,7 @@ func TestNewImportCommandSuccess(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc})) + cmd := newImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc})) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) diff --git a/cli/command/image/list.go b/cli/command/image/list.go index ce2238dc8298..b0ea568e050f 100644 --- a/cli/command/image/list.go +++ b/cli/command/image/list.go @@ -29,7 +29,14 @@ type imagesOptions struct { } // NewImagesCommand creates a new `docker images` command +// +// Deprecated: Do not import commands directly. They will be removed in a future release. func NewImagesCommand(dockerCLI command.Cli) *cobra.Command { + return newImagesCommand(dockerCLI) +} + +// newImagesCommand creates a new `docker images` command +func newImagesCommand(dockerCLI command.Cli) *cobra.Command { options := imagesOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ @@ -69,7 +76,7 @@ func NewImagesCommand(dockerCLI command.Cli) *cobra.Command { } func newListCommand(dockerCLI command.Cli) *cobra.Command { - cmd := *NewImagesCommand(dockerCLI) + cmd := *newImagesCommand(dockerCLI) cmd.Aliases = []string{"list"} cmd.Use = "ls [OPTIONS] [REPOSITORY[:TAG]]" return &cmd diff --git a/cli/command/image/list_test.go b/cli/command/image/list_test.go index 8b3ff715becf..06363857efe4 100644 --- a/cli/command/image/list_test.go +++ b/cli/command/image/list_test.go @@ -36,7 +36,7 @@ func TestNewImagesCommandErrors(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc})) + cmd := newImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc})) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) @@ -85,7 +85,7 @@ func TestNewImagesCommandSuccess(t *testing.T) { t.Run(tc.name, func(t *testing.T) { cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}) cli.SetConfigFile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat}) - cmd := NewImagesCommand(cli) + cmd := newImagesCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) @@ -104,7 +104,7 @@ func TestNewListCommandAlias(t *testing.T) { func TestNewListCommandAmbiguous(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cmd := NewImagesCommand(cli) + cmd := newImagesCommand(cli) cmd.SetOut(io.Discard) // Set the Use field to mimic that the command was called as "docker images", diff --git a/cli/command/image/load.go b/cli/command/image/load.go index 8d3d3906d0e0..268c18d2ef77 100644 --- a/cli/command/image/load.go +++ b/cli/command/image/load.go @@ -22,7 +22,14 @@ type loadOptions struct { } // NewLoadCommand creates a new `docker load` command -func NewLoadCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewLoadCommand(dockerCLI command.Cli) *cobra.Command { + return newLoadCommand(dockerCLI) +} + +// newLoadCommand creates a new `docker load` command +func newLoadCommand(dockerCLI command.Cli) *cobra.Command { var opts loadOptions cmd := &cobra.Command{ @@ -30,7 +37,7 @@ func NewLoadCommand(dockerCli command.Cli) *cobra.Command { Short: "Load an image from a tar archive or STDIN", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runLoad(cmd.Context(), dockerCli, opts) + return runLoad(cmd.Context(), dockerCLI, opts) }, Annotations: map[string]string{ "aliases": "docker image load, docker load", diff --git a/cli/command/image/load_test.go b/cli/command/image/load_test.go index d3a7dfd7dd98..143d4d59c9ba 100644 --- a/cli/command/image/load_test.go +++ b/cli/command/image/load_test.go @@ -54,7 +54,7 @@ func TestNewLoadCommandErrors(t *testing.T) { t.Run(tc.name, func(t *testing.T) { cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}) cli.In().SetIsTerminal(tc.isTerminalIn) - cmd := NewLoadCommand(cli) + cmd := newLoadCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) @@ -65,7 +65,7 @@ func TestNewLoadCommandErrors(t *testing.T) { func TestNewLoadCommandInvalidInput(t *testing.T) { expectedError := "open *" - cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{})) + cmd := newLoadCommand(test.NewFakeCli(&fakeClient{})) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs([]string{"--input", "*"}) @@ -117,7 +117,7 @@ func TestNewLoadCommandSuccess(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}) - cmd := NewLoadCommand(cli) + cmd := newLoadCommand(cli) cmd.SetOut(io.Discard) cmd.SetArgs(tc.args) err := cmd.Execute() diff --git a/cli/command/image/prune.go b/cli/command/image/prune.go index eec9b1c0ba92..f9db8761e17e 100644 --- a/cli/command/image/prune.go +++ b/cli/command/image/prune.go @@ -23,7 +23,14 @@ type pruneOptions struct { } // NewPruneCommand returns a new cobra prune command for images -func NewPruneCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewPruneCommand(dockerCLI command.Cli) *cobra.Command { + return newPruneCommand(dockerCLI) +} + +// newPruneCommand returns a new cobra prune command for images +func newPruneCommand(dockerCLI command.Cli) *cobra.Command { options := pruneOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ @@ -31,14 +38,14 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { Short: "Remove unused images", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCli, options) + spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCLI, options) if err != nil { return err } if output != "" { - fmt.Fprintln(dockerCli.Out(), output) + fmt.Fprintln(dockerCLI.Out(), output) } - fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) + fmt.Fprintln(dockerCLI.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) return nil }, Annotations: map[string]string{"version": "1.25"}, diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index c916bad24f3f..54f168b049b5 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -27,7 +27,14 @@ type pullOptions struct { } // NewPullCommand creates a new `docker pull` command -func NewPullCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewPullCommand(dockerCLI command.Cli) *cobra.Command { + return newPullCommand(dockerCLI) +} + +// newPullCommand creates a new `docker pull` command +func newPullCommand(dockerCLI command.Cli) *cobra.Command { var opts pullOptions cmd := &cobra.Command{ @@ -36,7 +43,7 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command { Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.remote = args[0] - return runPull(cmd.Context(), dockerCli, opts) + return runPull(cmd.Context(), dockerCLI, opts) }, Annotations: map[string]string{ "category-top": "5", @@ -51,7 +58,7 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command { flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output") addPlatformFlag(flags, &opts.platform) - flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") + flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification") _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) diff --git a/cli/command/image/pull_test.go b/cli/command/image/pull_test.go index a853949f32ca..ba7ff062133d 100644 --- a/cli/command/image/pull_test.go +++ b/cli/command/image/pull_test.go @@ -40,7 +40,7 @@ func TestNewPullCommandErrors(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cmd := NewPullCommand(cli) + cmd := newPullCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) @@ -79,7 +79,7 @@ func TestNewPullCommandSuccess(t *testing.T) { return io.NopCloser(strings.NewReader("")), nil }, }) - cmd := NewPullCommand(cli) + cmd := newPullCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) @@ -124,7 +124,7 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) { }, }, test.EnableContentTrust) cli.SetNotaryClient(tc.notaryFunc) - cmd := NewPullCommand(cli) + cmd := newPullCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) diff --git a/cli/command/image/push.go b/cli/command/image/push.go index 1ef31aedd7fd..480ee0d31b64 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -36,7 +36,14 @@ type pushOptions struct { } // NewPushCommand creates a new `docker push` command -func NewPushCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewPushCommand(dockerCLI command.Cli) *cobra.Command { + return newPushCommand(dockerCLI) +} + +// newPushCommand creates a new `docker push` command +func newPushCommand(dockerCLI command.Cli) *cobra.Command { var opts pushOptions cmd := &cobra.Command{ @@ -45,19 +52,19 @@ func NewPushCommand(dockerCli command.Cli) *cobra.Command { Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.remote = args[0] - return runPush(cmd.Context(), dockerCli, opts) + return runPush(cmd.Context(), dockerCLI, opts) }, Annotations: map[string]string{ "category-top": "6", "aliases": "docker image push, docker push", }, - ValidArgsFunction: completion.ImageNames(dockerCli, 1), + ValidArgsFunction: completion.ImageNames(dockerCLI, 1), } flags := cmd.Flags() flags.BoolVarP(&opts.all, "all-tags", "a", false, "Push all tags of an image to the repository") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output") - flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image signing") + flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image signing") // Don't default to DOCKER_DEFAULT_PLATFORM env variable, always default to // pushing the image as-is. This also avoids forcing the platform selection diff --git a/cli/command/image/push_test.go b/cli/command/image/push_test.go index 88415ea8e0df..5c43b94fcb38 100644 --- a/cli/command/image/push_test.go +++ b/cli/command/image/push_test.go @@ -40,7 +40,7 @@ func TestNewPushCommandErrors(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc}) - cmd := NewPushCommand(cli) + cmd := newPushCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) @@ -73,7 +73,7 @@ func TestNewPushCommandSuccess(t *testing.T) { return io.NopCloser(strings.NewReader("")), nil }, }) - cmd := NewPushCommand(cli) + cmd := newPushCommand(cli) cmd.SetOut(cli.OutBuffer()) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) diff --git a/cli/command/image/remove.go b/cli/command/image/remove.go index 977a297ae7d4..2974897fe421 100644 --- a/cli/command/image/remove.go +++ b/cli/command/image/remove.go @@ -21,7 +21,14 @@ type removeOptions struct { } // NewRemoveCommand creates a new `docker remove` command +// +// Deprecated: Do not import commands directly. They will be removed in a future release. func NewRemoveCommand(dockerCLI command.Cli) *cobra.Command { + return newRemoveCommand(dockerCLI) +} + +// newRemoveCommand creates a new `docker remove` command +func newRemoveCommand(dockerCLI command.Cli) *cobra.Command { var options removeOptions cmd := &cobra.Command{ @@ -50,8 +57,9 @@ func NewRemoveCommand(dockerCLI command.Cli) *cobra.Command { return cmd } -func newRemoveCommand(dockerCli command.Cli) *cobra.Command { - cmd := *NewRemoveCommand(dockerCli) +// newImageRemoveCommand is a sub-command under `image` (`docker image rm`) +func newImageRemoveCommand(dockerCli command.Cli) *cobra.Command { + cmd := *newRemoveCommand(dockerCli) cmd.Aliases = []string{"rmi", "remove"} cmd.Use = "rm [OPTIONS] IMAGE [IMAGE...]" return &cmd diff --git a/cli/command/image/remove_test.go b/cli/command/image/remove_test.go index 16c0a3c4a1c6..4ffc7273be4e 100644 --- a/cli/command/image/remove_test.go +++ b/cli/command/image/remove_test.go @@ -24,7 +24,7 @@ func (n notFound) Error() string { func (notFound) NotFound() {} func TestNewRemoveCommandAlias(t *testing.T) { - cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{})) + cmd := newImageRemoveCommand(test.NewFakeCli(&fakeClient{})) assert.Check(t, cmd.HasAlias("rmi")) assert.Check(t, cmd.HasAlias("remove")) assert.Check(t, !cmd.HasAlias("other")) @@ -63,7 +63,7 @@ func TestNewRemoveCommandErrors(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{ + cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{ imageRemoveFunc: tc.imageRemoveFunc, })) cmd.SetOut(io.Discard) @@ -122,7 +122,7 @@ func TestNewRemoveCommandSuccess(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc}) - cmd := NewRemoveCommand(cli) + cmd := newRemoveCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) diff --git a/cli/command/image/save.go b/cli/command/image/save.go index 64bd72a74d8d..9ffad043dc4d 100644 --- a/cli/command/image/save.go +++ b/cli/command/image/save.go @@ -21,7 +21,14 @@ type saveOptions struct { } // NewSaveCommand creates a new `docker save` command -func NewSaveCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewSaveCommand(dockerCLI command.Cli) *cobra.Command { + return newSaveCommand(dockerCLI) +} + +// newSaveCommand creates a new `docker save` command +func newSaveCommand(dockerCLI command.Cli) *cobra.Command { var opts saveOptions cmd := &cobra.Command{ @@ -30,12 +37,12 @@ func NewSaveCommand(dockerCli command.Cli) *cobra.Command { Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.images = args - return runSave(cmd.Context(), dockerCli, opts) + return runSave(cmd.Context(), dockerCLI, opts) }, Annotations: map[string]string{ "aliases": "docker image save, docker save", }, - ValidArgsFunction: completion.ImageNames(dockerCli, -1), + ValidArgsFunction: completion.ImageNames(dockerCLI, -1), } flags := cmd.Flags() diff --git a/cli/command/image/save_test.go b/cli/command/image/save_test.go index 7a3e93eb3547..f3104cc3f75c 100644 --- a/cli/command/image/save_test.go +++ b/cli/command/image/save_test.go @@ -61,7 +61,7 @@ func TestNewSaveCommandErrors(t *testing.T) { t.Run(tc.name, func(t *testing.T) { cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc}) cli.Out().SetIsTerminal(tc.isTerminal) - cmd := NewSaveCommand(cli) + cmd := newSaveCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) @@ -114,7 +114,7 @@ func TestNewSaveCommandSuccess(t *testing.T) { } for _, tc := range testCases { t.Run(strings.Join(tc.args, " "), func(t *testing.T) { - cmd := NewSaveCommand(test.NewFakeCli(&fakeClient{ + cmd := newSaveCommand(test.NewFakeCli(&fakeClient{ imageSaveFunc: tc.imageSaveFunc, })) cmd.SetOut(io.Discard) diff --git a/cli/command/image/tag.go b/cli/command/image/tag.go index 7a495afca044..0eb345e2dea8 100644 --- a/cli/command/image/tag.go +++ b/cli/command/image/tag.go @@ -15,7 +15,14 @@ type tagOptions struct { } // NewTagCommand creates a new `docker tag` command -func NewTagCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewTagCommand(dockerCLI command.Cli) *cobra.Command { + return newTagCommand(dockerCLI) +} + +// newTagCommand creates a new `docker tag` command +func newTagCommand(dockerCli command.Cli) *cobra.Command { var opts tagOptions cmd := &cobra.Command{ diff --git a/cli/command/image/tag_test.go b/cli/command/image/tag_test.go index 65ceb60323ed..9f67c8d69e24 100644 --- a/cli/command/image/tag_test.go +++ b/cli/command/image/tag_test.go @@ -17,7 +17,7 @@ func TestCliNewTagCommandErrors(t *testing.T) { } expectedError := "'tag' requires 2 arguments" for _, args := range testCases { - cmd := NewTagCommand(test.NewFakeCli(&fakeClient{})) + cmd := newTagCommand(test.NewFakeCli(&fakeClient{})) cmd.SetArgs(args) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) @@ -26,7 +26,7 @@ func TestCliNewTagCommandErrors(t *testing.T) { } func TestCliNewTagCommand(t *testing.T) { - cmd := NewTagCommand( + cmd := newTagCommand( test.NewFakeCli(&fakeClient{ imageTagFunc: func(image string, ref string) error { assert.Check(t, is.Equal("image1", image)) From def5bfc11cfc43322ff31bc096baaca363d736f9 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:12:45 +0200 Subject: [PATCH 066/193] Unexport system commands This patch deprecates exported system commands and moves the implementation details to an unexported function. Commands that are affected include: - system.NewVersionCommand - system.NewInfoCommand - system.NewSystemCommand - system.NewEventsCommand - system.NewInspectCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit cfb8cb91f2e065475f5b693282d618081ed7b945) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 5 +++++ cli/command/system/cmd.go | 21 ++++++++++++++------- cli/command/system/completion_test.go | 2 +- cli/command/system/events.go | 13 ++++++++++--- cli/command/system/events_test.go | 2 +- cli/command/system/info.go | 11 +++++++++-- cli/command/system/inspect.go | 11 +++++++++-- cli/command/system/inspect_test.go | 2 +- cli/command/system/version.go | 11 +++++++++-- cli/command/system/version_test.go | 2 +- 10 files changed, 60 insertions(+), 20 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index a735e36d8ccd..8abe3a3ac44a 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -46,7 +46,9 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { registry.NewLoginCommand(dockerCli), registry.NewLogoutCommand(dockerCli), registry.NewSearchCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) system.NewVersionCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) system.NewInfoCommand(dockerCli), // management commands @@ -64,6 +66,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { manifest.NewManifestCommand(dockerCli), network.NewNetworkCommand(dockerCli), plugin.NewPluginCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) system.NewSystemCommand(dockerCli), trust.NewTrustCommand(dockerCli), volume.NewVolumeCommand(dockerCli), @@ -130,7 +133,9 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { hide(image.NewSaveCommand(dockerCli)), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(image.NewTagCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(system.NewEventsCommand(dockerCli)), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) hide(system.NewInspectCommand(dockerCli)), ) } diff --git a/cli/command/system/cmd.go b/cli/command/system/cmd.go index 6accb98f0c4e..2d0dcf441d11 100644 --- a/cli/command/system/cmd.go +++ b/cli/command/system/cmd.go @@ -7,19 +7,26 @@ import ( ) // NewSystemCommand returns a cobra command for `system` subcommands -func NewSystemCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewSystemCommand(dockerCLI command.Cli) *cobra.Command { + return newSystemCommand(dockerCLI) +} + +// newSystemCommand returns a cobra command for `system` subcommands +func newSystemCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "system", Short: "Manage Docker", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), } cmd.AddCommand( - NewEventsCommand(dockerCli), - NewInfoCommand(dockerCli), - newDiskUsageCommand(dockerCli), - newPruneCommand(dockerCli), - newDialStdioCommand(dockerCli), + newEventsCommand(dockerCLI), + newInfoCommand(dockerCLI), + newDiskUsageCommand(dockerCLI), + newPruneCommand(dockerCLI), + newDialStdioCommand(dockerCLI), ) return cmd diff --git a/cli/command/system/completion_test.go b/cli/command/system/completion_test.go index 3e703a6a49b2..7872a7a58256 100644 --- a/cli/command/system/completion_test.go +++ b/cli/command/system/completion_test.go @@ -156,7 +156,7 @@ func TestCompleteEventFilter(t *testing.T) { for _, tc := range tests { cli := test.NewFakeCli(tc.client) - completions, directive := completeEventFilters(cli)(NewEventsCommand(cli), nil, tc.toComplete) + completions, directive := completeEventFilters(cli)(newEventsCommand(cli), nil, tc.toComplete) assert.DeepEqual(t, completions, tc.expected) assert.Equal(t, directive, cobra.ShellCompDirectiveNoFileComp, fmt.Sprintf("wrong directive in completion for '%s'", tc.toComplete)) diff --git a/cli/command/system/events.go b/cli/command/system/events.go index d83d36351e33..d2f8750ca0be 100644 --- a/cli/command/system/events.go +++ b/cli/command/system/events.go @@ -28,7 +28,14 @@ type eventsOptions struct { } // NewEventsCommand creates a new cobra.Command for `docker events` -func NewEventsCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewEventsCommand(dockerCLI command.Cli) *cobra.Command { + return newEventsCommand(dockerCLI) +} + +// newEventsCommand creates a new cobra.Command for `docker events` +func newEventsCommand(dockerCLI command.Cli) *cobra.Command { options := eventsOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ @@ -36,7 +43,7 @@ func NewEventsCommand(dockerCli command.Cli) *cobra.Command { Short: "Get real time events from the server", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runEvents(cmd.Context(), dockerCli, &options) + return runEvents(cmd.Context(), dockerCLI, &options) }, Annotations: map[string]string{ "aliases": "docker system events, docker events", @@ -50,7 +57,7 @@ func NewEventsCommand(dockerCli command.Cli) *cobra.Command { flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") flags.StringVar(&options.format, "format", "", flagsHelper.InspectFormatHelp) // using the same flag description as "inspect" commands for now. - _ = cmd.RegisterFlagCompletionFunc("filter", completeEventFilters(dockerCli)) + _ = cmd.RegisterFlagCompletionFunc("filter", completeEventFilters(dockerCLI)) return cmd } diff --git a/cli/command/system/events_test.go b/cli/command/system/events_test.go index 847605a44860..bf00acf938c4 100644 --- a/cli/command/system/events_test.go +++ b/cli/command/system/events_test.go @@ -70,7 +70,7 @@ func TestEventsFormat(t *testing.T) { }() return messages, errs }}) - cmd := NewEventsCommand(cli) + cmd := newEventsCommand(cli) cmd.SetArgs(tc.args) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) diff --git a/cli/command/system/info.go b/cli/command/system/info.go index c892961402d8..350431c8386c 100644 --- a/cli/command/system/info.go +++ b/cli/command/system/info.go @@ -60,7 +60,14 @@ func (i *dockerInfo) clientPlatform() string { } // NewInfoCommand creates a new cobra.Command for `docker info` -func NewInfoCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewInfoCommand(dockerCLI command.Cli) *cobra.Command { + return newInfoCommand(dockerCLI) +} + +// newInfoCommand creates a new cobra.Command for `docker info` +func newInfoCommand(dockerCLI command.Cli) *cobra.Command { var opts infoOptions cmd := &cobra.Command{ @@ -68,7 +75,7 @@ func NewInfoCommand(dockerCli command.Cli) *cobra.Command { Short: "Display system-wide information", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runInfo(cmd.Context(), cmd, dockerCli, &opts) + return runInfo(cmd.Context(), cmd, dockerCLI, &opts) }, Annotations: map[string]string{ "category-top": "12", diff --git a/cli/command/system/inspect.go b/cli/command/system/inspect.go index 1765538e2fa4..aa34169b9f3d 100644 --- a/cli/command/system/inspect.go +++ b/cli/command/system/inspect.go @@ -60,7 +60,14 @@ type inspectOptions struct { } // NewInspectCommand creates a new cobra.Command for `docker inspect` -func NewInspectCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewInspectCommand(dockerCLI command.Cli) *cobra.Command { + return newInspectCommand(dockerCLI) +} + +// newInspectCommand creates a new cobra.Command for `docker inspect` +func newInspectCommand(dockerCLI command.Cli) *cobra.Command { var opts inspectOptions cmd := &cobra.Command{ @@ -72,7 +79,7 @@ func NewInspectCommand(dockerCli command.Cli) *cobra.Command { if cmd.Flags().Changed("type") && opts.objectType == "" { return fmt.Errorf(`type is empty: must be one of "%s"`, strings.Join(allTypes, `", "`)) } - return runInspect(cmd.Context(), dockerCli, opts) + return runInspect(cmd.Context(), dockerCLI, opts) }, // TODO(thaJeztah): should we consider adding completion for common object-types? (images, containers?) ValidArgsFunction: completion.NoComplete, diff --git a/cli/command/system/inspect_test.go b/cli/command/system/inspect_test.go index 56ad5fd5b588..3fbadc7f6d0f 100644 --- a/cli/command/system/inspect_test.go +++ b/cli/command/system/inspect_test.go @@ -32,7 +32,7 @@ func TestInspectValidateFlagsAndArgs(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - cmd := NewInspectCommand(test.NewFakeCli(&fakeClient{})) + cmd := newInspectCommand(test.NewFakeCli(&fakeClient{})) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) diff --git a/cli/command/system/version.go b/cli/command/system/version.go index 3a0ad75c351e..a1839ff64135 100644 --- a/cli/command/system/version.go +++ b/cli/command/system/version.go @@ -109,7 +109,14 @@ func newClientVersion(contextName string, dockerCli command.Cli) clientVersion { } // NewVersionCommand creates a new cobra.Command for `docker version` -func NewVersionCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewVersionCommand(dockerCLI command.Cli) *cobra.Command { + return newVersionCommand(dockerCLI) +} + +// newVersionCommand creates a new cobra.Command for `docker version` +func newVersionCommand(dockerCLI command.Cli) *cobra.Command { var opts versionOptions cmd := &cobra.Command{ @@ -117,7 +124,7 @@ func NewVersionCommand(dockerCli command.Cli) *cobra.Command { Short: "Show the Docker version information", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runVersion(cmd.Context(), dockerCli, &opts) + return runVersion(cmd.Context(), dockerCLI, &opts) }, Annotations: map[string]string{ "category-top": "10", diff --git a/cli/command/system/version_test.go b/cli/command/system/version_test.go index 6f8b243de4b4..7b060e843720 100644 --- a/cli/command/system/version_test.go +++ b/cli/command/system/version_test.go @@ -20,7 +20,7 @@ func TestVersionWithoutServer(t *testing.T) { return types.Version{}, errors.New("no server") }, }) - cmd := NewVersionCommand(cli) + cmd := newVersionCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(cli.Err()) cmd.SetErr(io.Discard) From ba8b22e783470eeb78f92f33a58f75aa90b09f09 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:20:27 +0200 Subject: [PATCH 067/193] Unexport network commands This patch deprecates exported network commands and moves the implementation details to an unexported function. Commands that are affected include: - network.NewNetworkCommand - network.NewPruneCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 78a8856c1459d920f7a3642855bd774dbd16f021) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 1 + cli/command/network/cmd.go | 25 ++++++++++++++++--------- cli/command/network/prune.go | 13 ++++++++++--- cli/command/network/prune_test.go | 2 +- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index 8abe3a3ac44a..22a9905dde46 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -64,6 +64,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) image.NewImageCommand(dockerCli), manifest.NewManifestCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) network.NewNetworkCommand(dockerCli), plugin.NewPluginCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) diff --git a/cli/command/network/cmd.go b/cli/command/network/cmd.go index 3db3088ebe95..140702667358 100644 --- a/cli/command/network/cmd.go +++ b/cli/command/network/cmd.go @@ -7,22 +7,29 @@ import ( ) // NewNetworkCommand returns a cobra command for `network` subcommands -func NewNetworkCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewNetworkCommand(dockerCLI command.Cli) *cobra.Command { + return newNetworkCommand(dockerCLI) +} + +// newNetworkCommand returns a cobra command for `network` subcommands +func newNetworkCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "network", Short: "Manage networks", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), Annotations: map[string]string{"version": "1.21"}, } cmd.AddCommand( - newConnectCommand(dockerCli), - newCreateCommand(dockerCli), - newDisconnectCommand(dockerCli), - newInspectCommand(dockerCli), - newListCommand(dockerCli), - newRemoveCommand(dockerCli), - NewPruneCommand(dockerCli), + newConnectCommand(dockerCLI), + newCreateCommand(dockerCLI), + newDisconnectCommand(dockerCLI), + newInspectCommand(dockerCLI), + newListCommand(dockerCLI), + newRemoveCommand(dockerCLI), + newPruneCommand(dockerCLI), ) return cmd } diff --git a/cli/command/network/prune.go b/cli/command/network/prune.go index fce7adf3ed9a..0752c39b6e73 100644 --- a/cli/command/network/prune.go +++ b/cli/command/network/prune.go @@ -18,7 +18,14 @@ type pruneOptions struct { } // NewPruneCommand returns a new cobra prune command for networks -func NewPruneCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewPruneCommand(dockerCLI command.Cli) *cobra.Command { + return newPruneCommand(dockerCLI) +} + +// newPruneCommand returns a new cobra prune command for networks +func newPruneCommand(dockerCLI command.Cli) *cobra.Command { options := pruneOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ @@ -26,12 +33,12 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { Short: "Remove all unused networks", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - output, err := runPrune(cmd.Context(), dockerCli, options) + output, err := runPrune(cmd.Context(), dockerCLI, options) if err != nil { return err } if output != "" { - _, _ = fmt.Fprintln(dockerCli.Out(), output) + _, _ = fmt.Fprintln(dockerCLI.Out(), output) } return nil }, diff --git a/cli/command/network/prune_test.go b/cli/command/network/prune_test.go index 67a57e8c8af1..ee54108f772e 100644 --- a/cli/command/network/prune_test.go +++ b/cli/command/network/prune_test.go @@ -20,7 +20,7 @@ func TestNetworkPrunePromptTermination(t *testing.T) { return network.PruneReport{}, errors.New("fakeClient networkPruneFunc should not be called") }, }) - cmd := NewPruneCommand(cli) + cmd := newPruneCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) From c580c9741e26dacf5687cf8a2cb10738e1b673a6 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:34:49 +0200 Subject: [PATCH 068/193] Unexport node commands This patch deprecates exported node commands and moves the implementation details to an unexported function. Commands that are affected include: - node.NewNodeCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit ab3fcf9f9b72dcd8e80688598b6fdd4d6afe9158) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 1 + cli/command/node/cmd.go | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index 22a9905dde46..70f7064fd8aa 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -75,6 +75,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { // orchestration (swarm) commands //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) config.NewConfigCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) node.NewNodeCommand(dockerCli), secret.NewSecretCommand(dockerCli), service.NewServiceCommand(dockerCli), diff --git a/cli/command/node/cmd.go b/cli/command/node/cmd.go index 655f0170b756..4536ae7f41bd 100644 --- a/cli/command/node/cmd.go +++ b/cli/command/node/cmd.go @@ -12,25 +12,32 @@ import ( ) // NewNodeCommand returns a cobra command for `node` subcommands -func NewNodeCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewNodeCommand(dockerCLI command.Cli) *cobra.Command { + return newNodeCommand(dockerCLI) +} + +// newNodeCommand returns a cobra command for `node` subcommands +func newNodeCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "node", Short: "Manage Swarm nodes", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), Annotations: map[string]string{ "version": "1.24", "swarm": "manager", }, } cmd.AddCommand( - newDemoteCommand(dockerCli), - newInspectCommand(dockerCli), - newListCommand(dockerCli), - newPromoteCommand(dockerCli), - newRemoveCommand(dockerCli), - newPsCommand(dockerCli), - newUpdateCommand(dockerCli), + newDemoteCommand(dockerCLI), + newInspectCommand(dockerCLI), + newListCommand(dockerCLI), + newPromoteCommand(dockerCLI), + newRemoveCommand(dockerCLI), + newPsCommand(dockerCLI), + newUpdateCommand(dockerCLI), ) return cmd } From 6fc62cf5b68322826d0587f88dea65eeca27b4ec Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:40:26 +0200 Subject: [PATCH 069/193] Unexport manifest command This patch deprecates exported manifest commands and moves the implementation details to an unexported function. Commands that are affected include: - manifest.NewManifestCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 02fda07211c6125410e6aa2f9bb2641852a5db37) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 1 + cli/command/manifest/cmd.go | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index 70f7064fd8aa..abd329ee603f 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -63,6 +63,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { context.NewContextCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) image.NewImageCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) manifest.NewManifestCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) network.NewNetworkCommand(dockerCli), diff --git a/cli/command/manifest/cmd.go b/cli/command/manifest/cmd.go index 939f02b7bc5c..46d1b0d668c7 100644 --- a/cli/command/manifest/cmd.go +++ b/cli/command/manifest/cmd.go @@ -10,7 +10,14 @@ import ( ) // NewManifestCommand returns a cobra command for `manifest` subcommands -func NewManifestCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewManifestCommand(dockerCLI command.Cli) *cobra.Command { + return newManifestCommand(dockerCLI) +} + +// newManifestCommand returns a cobra command for `manifest` subcommands +func newManifestCommand(dockerCLI command.Cli) *cobra.Command { // use dockerCli as command.Cli cmd := &cobra.Command{ Use: "manifest COMMAND", @@ -18,16 +25,16 @@ func NewManifestCommand(dockerCli command.Cli) *cobra.Command { Long: manifestDescription, Args: cli.NoArgs, Run: func(cmd *cobra.Command, args []string) { - _, _ = fmt.Fprint(dockerCli.Err(), "\n"+cmd.UsageString()) + _, _ = fmt.Fprint(dockerCLI.Err(), "\n"+cmd.UsageString()) }, Annotations: map[string]string{"experimentalCLI": ""}, } cmd.AddCommand( - newCreateListCommand(dockerCli), - newInspectCommand(dockerCli), - newAnnotateCommand(dockerCli), - newPushListCommand(dockerCli), - newRmManifestListCommand(dockerCli), + newCreateListCommand(dockerCLI), + newInspectCommand(dockerCLI), + newAnnotateCommand(dockerCLI), + newPushListCommand(dockerCLI), + newRmManifestListCommand(dockerCLI), ) return cmd } From 0f2721ee4fa230112dba5d167e4ece5ecf6c9e5b Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:45:11 +0200 Subject: [PATCH 070/193] Unexport secret commands This patch deprecates exported secret commands and moves the implementation details to an unexported function. Commands that are affected include: - secrets.NewSecretCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit e00762ed7d5f929ee75f9dd95e4ee1ca7dd02f0d) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 1 + cli/command/secret/cmd.go | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index abd329ee603f..a7e41c66ec31 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -78,6 +78,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { config.NewConfigCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) node.NewNodeCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) secret.NewSecretCommand(dockerCli), service.NewServiceCommand(dockerCli), stack.NewStackCommand(dockerCli), diff --git a/cli/command/secret/cmd.go b/cli/command/secret/cmd.go index f31348a7e87f..2fb8aa60e18c 100644 --- a/cli/command/secret/cmd.go +++ b/cli/command/secret/cmd.go @@ -9,22 +9,28 @@ import ( ) // NewSecretCommand returns a cobra command for `secret` subcommands -func NewSecretCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewSecretCommand(dockerCLI command.Cli) *cobra.Command { + return newSecretCommand(dockerCLI) +} + +func newSecretCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "secret", Short: "Manage Swarm secrets", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), Annotations: map[string]string{ "version": "1.25", "swarm": "manager", }, } cmd.AddCommand( - newSecretListCommand(dockerCli), - newSecretCreateCommand(dockerCli), - newSecretInspectCommand(dockerCli), - newSecretRemoveCommand(dockerCli), + newSecretListCommand(dockerCLI), + newSecretCreateCommand(dockerCLI), + newSecretInspectCommand(dockerCLI), + newSecretRemoveCommand(dockerCLI), ) return cmd } From 01ea3a7da745094cbb1ccc460a18d7ae3f9c0d38 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:51:55 +0200 Subject: [PATCH 071/193] Unexport service commands This patch deprecates exported service commands and moves the implementation details to an unexported function. Commands that are affected include: - service.NewServiceCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 88178eda32b1fd64b4b1ea0ea1c5a194d6cf6e17) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 1 + cli/command/service/cmd.go | 29 ++++++++++++++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index a7e41c66ec31..7c3504c629d4 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -80,6 +80,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { node.NewNodeCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) secret.NewSecretCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) service.NewServiceCommand(dockerCli), stack.NewStackCommand(dockerCli), swarm.NewSwarmCommand(dockerCli), diff --git a/cli/command/service/cmd.go b/cli/command/service/cmd.go index 6b6626cc1443..52713726a074 100644 --- a/cli/command/service/cmd.go +++ b/cli/command/service/cmd.go @@ -7,27 +7,34 @@ import ( ) // NewServiceCommand returns a cobra command for `service` subcommands -func NewServiceCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewServiceCommand(dockerCLI command.Cli) *cobra.Command { + return newServiceCommand(dockerCLI) +} + +// newServiceCommand returns a cobra command for `service` subcommands +func newServiceCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "service", Short: "Manage Swarm services", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), Annotations: map[string]string{ "version": "1.24", "swarm": "manager", }, } cmd.AddCommand( - newCreateCommand(dockerCli), - newInspectCommand(dockerCli), - newPsCommand(dockerCli), - newListCommand(dockerCli), - newRemoveCommand(dockerCli), - newScaleCommand(dockerCli), - newUpdateCommand(dockerCli), - newLogsCommand(dockerCli), - newRollbackCommand(dockerCli), + newCreateCommand(dockerCLI), + newInspectCommand(dockerCLI), + newPsCommand(dockerCLI), + newListCommand(dockerCLI), + newRemoveCommand(dockerCLI), + newScaleCommand(dockerCLI), + newUpdateCommand(dockerCLI), + newLogsCommand(dockerCLI), + newRollbackCommand(dockerCLI), ) return cmd } From 3e36fa624a56c83466704b4d43c7710c997d93dd Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:57:33 +0200 Subject: [PATCH 072/193] Unexport volume commands This patch deprecates exported volume commands and moves the implementation details to an unexported function. Commands that are affected include: - volume.NewVolumeCommand - volume.NewPruneCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 9961e39d40d02694711bf5f4701dc4d5bd06247b) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 1 + cli/command/volume/cmd.go | 23 +++++++++++++++-------- cli/command/volume/prune.go | 15 +++++++++++---- cli/command/volume/prune_test.go | 12 ++++++------ 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index 7c3504c629d4..cae16c0e013b 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -71,6 +71,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) system.NewSystemCommand(dockerCli), trust.NewTrustCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) volume.NewVolumeCommand(dockerCli), // orchestration (swarm) commands diff --git a/cli/command/volume/cmd.go b/cli/command/volume/cmd.go index 386352791e2f..f1065d49a507 100644 --- a/cli/command/volume/cmd.go +++ b/cli/command/volume/cmd.go @@ -7,21 +7,28 @@ import ( ) // NewVolumeCommand returns a cobra command for `volume` subcommands -func NewVolumeCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewVolumeCommand(dockerCLI command.Cli) *cobra.Command { + return newVolumeCommand(dockerCLI) +} + +// newVolumeCommand returns a cobra command for `volume` subcommands +func newVolumeCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "volume COMMAND", Short: "Manage volumes", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), Annotations: map[string]string{"version": "1.21"}, } cmd.AddCommand( - newCreateCommand(dockerCli), - newInspectCommand(dockerCli), - newListCommand(dockerCli), - newRemoveCommand(dockerCli), - NewPruneCommand(dockerCli), - newUpdateCommand(dockerCli), + newCreateCommand(dockerCLI), + newInspectCommand(dockerCLI), + newListCommand(dockerCLI), + newRemoveCommand(dockerCLI), + newPruneCommand(dockerCLI), + newUpdateCommand(dockerCLI), ) return cmd } diff --git a/cli/command/volume/prune.go b/cli/command/volume/prune.go index 0765e0228948..1f90292d94a6 100644 --- a/cli/command/volume/prune.go +++ b/cli/command/volume/prune.go @@ -22,7 +22,14 @@ type pruneOptions struct { } // NewPruneCommand returns a new cobra prune command for volumes -func NewPruneCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewPruneCommand(dockerCLI command.Cli) *cobra.Command { + return newPruneCommand(dockerCLI) +} + +// newPruneCommand returns a new cobra prune command for volumes +func newPruneCommand(dockerCLI command.Cli) *cobra.Command { options := pruneOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ @@ -30,14 +37,14 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { Short: "Remove unused local volumes", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCli, options) + spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCLI, options) if err != nil { return err } if output != "" { - fmt.Fprintln(dockerCli.Out(), output) + fmt.Fprintln(dockerCLI.Out(), output) } - fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) + fmt.Fprintln(dockerCLI.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) return nil }, Annotations: map[string]string{"version": "1.25"}, diff --git a/cli/command/volume/prune_test.go b/cli/command/volume/prune_test.go index 11ee8adef879..5c31c9cc001c 100644 --- a/cli/command/volume/prune_test.go +++ b/cli/command/volume/prune_test.go @@ -53,7 +53,7 @@ func TestVolumePruneErrors(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - cmd := NewPruneCommand( + cmd := newPruneCommand( test.NewFakeCli(&fakeClient{ volumePruneFunc: tc.volumePruneFunc, }), @@ -105,7 +105,7 @@ func TestVolumePruneSuccess(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cli := test.NewFakeCli(&fakeClient{volumePruneFunc: tc.volumePruneFunc}) - cmd := NewPruneCommand(cli) + cmd := newPruneCommand(cli) if tc.input != "" { cli.SetIn(streams.NewIn(io.NopCloser(strings.NewReader(tc.input)))) } @@ -135,7 +135,7 @@ func TestVolumePruneForce(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ volumePruneFunc: tc.volumePruneFunc, }) - cmd := NewPruneCommand(cli) + cmd := newPruneCommand(cli) cmd.Flags().Set("force", "true") assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("volume-prune.%s.golden", tc.name)) @@ -152,7 +152,7 @@ func TestVolumePrunePromptYes(t *testing.T) { }) cli.SetIn(streams.NewIn(io.NopCloser(strings.NewReader(input)))) - cmd := NewPruneCommand(cli) + cmd := newPruneCommand(cli) cmd.SetArgs([]string{}) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "volume-prune-yes.golden") @@ -170,7 +170,7 @@ func TestVolumePrunePromptNo(t *testing.T) { }) cli.SetIn(streams.NewIn(io.NopCloser(strings.NewReader(input)))) - cmd := NewPruneCommand(cli) + cmd := newPruneCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) @@ -199,7 +199,7 @@ func TestVolumePrunePromptTerminate(t *testing.T) { }, }) - cmd := NewPruneCommand(cli) + cmd := newPruneCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) From 82de0590b618bcddc36bf5019a7441df33af0397 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:22:47 +0200 Subject: [PATCH 073/193] Unexport context command This patch deprecates exported context commands and moves the implementation details to an unexported function. Commands that are affected include: - context.NewContextCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 3b0edc794c1a4fedb910e900cc51839a0d0c6ac4) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 1 + cli/command/context/cmd.go | 29 ++++++++++++++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index cae16c0e013b..92633fbeac76 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -60,6 +60,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { checkpoint.NewCheckpointCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) container.NewContainerCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) context.NewContextCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) image.NewImageCommand(dockerCli), diff --git a/cli/command/context/cmd.go b/cli/command/context/cmd.go index f8b9e80d9399..f50e0f248f2c 100644 --- a/cli/command/context/cmd.go +++ b/cli/command/context/cmd.go @@ -7,23 +7,30 @@ import ( ) // NewContextCommand returns the context cli subcommand -func NewContextCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewContextCommand(dockerCLI command.Cli) *cobra.Command { + return newContextCommand(dockerCLI) +} + +// newContextCommand returns the context cli subcommand +func newContextCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "context", Short: "Manage contexts", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), } cmd.AddCommand( - newCreateCommand(dockerCli), - newListCommand(dockerCli), - newUseCommand(dockerCli), - newExportCommand(dockerCli), - newImportCommand(dockerCli), - newRemoveCommand(dockerCli), - newUpdateCommand(dockerCli), - newInspectCommand(dockerCli), - newShowCommand(dockerCli), + newCreateCommand(dockerCLI), + newListCommand(dockerCLI), + newUseCommand(dockerCLI), + newExportCommand(dockerCLI), + newImportCommand(dockerCLI), + newRemoveCommand(dockerCLI), + newUpdateCommand(dockerCLI), + newInspectCommand(dockerCLI), + newShowCommand(dockerCLI), ) return cmd } From 4a2b024c57c1c85c5d4d38d3bef1f0fd5b3ff678 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:27:11 +0200 Subject: [PATCH 074/193] Unexport stack commands This patch deprecates exported stack commands and moves the implementation details to an unexported function. Commands that are affected include: - stack.NewStackCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 630fe430ffb7ecd3574a1eeea9ec7c1d17abe390) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 1 + cli/command/stack/cmd.go | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index 92633fbeac76..36883287ce18 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -84,6 +84,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { secret.NewSecretCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) service.NewServiceCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) stack.NewStackCommand(dockerCli), swarm.NewSwarmCommand(dockerCli), diff --git a/cli/command/stack/cmd.go b/cli/command/stack/cmd.go index e46f49ad9740..17efafb4c005 100644 --- a/cli/command/stack/cmd.go +++ b/cli/command/stack/cmd.go @@ -11,12 +11,19 @@ import ( ) // NewStackCommand returns a cobra command for `stack` subcommands -func NewStackCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewStackCommand(dockerCLI command.Cli) *cobra.Command { + return newStackCommand(dockerCLI) +} + +// newStackCommand returns a cobra command for `stack` subcommands +func newStackCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "stack [OPTIONS]", Short: "Manage Swarm stacks", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), Annotations: map[string]string{ "version": "1.25", "swarm": "manager", @@ -25,18 +32,18 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command { defaultHelpFunc := cmd.HelpFunc() cmd.SetHelpFunc(func(c *cobra.Command, args []string) { if err := cmd.Root().PersistentPreRunE(c, args); err != nil { - fmt.Fprintln(dockerCli.Err(), err) + fmt.Fprintln(dockerCLI.Err(), err) return } defaultHelpFunc(c, args) }) cmd.AddCommand( - newDeployCommand(dockerCli), - newListCommand(dockerCli), - newPsCommand(dockerCli), - newRemoveCommand(dockerCli), - newServicesCommand(dockerCli), - newConfigCommand(dockerCli), + newDeployCommand(dockerCLI), + newListCommand(dockerCLI), + newPsCommand(dockerCLI), + newRemoveCommand(dockerCLI), + newServicesCommand(dockerCLI), + newConfigCommand(dockerCLI), ) flags := cmd.PersistentFlags() flags.String("orchestrator", "", "Orchestrator to use (swarm|all)") From 46862878f726f2cabb1da47164c642673d29d6a4 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:35:44 +0200 Subject: [PATCH 075/193] Unexport registry commands This patch deprecates exported registry commands and moves the implementation details to an unexported function. Commands that are affected include: - registry.NewLoginCommand - registry.NewLogoutCommand - registry.NewSearchCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit d4588c711c8643547d9f7d7e68babbfe51700da6) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 3 +++ cli/command/registry/login.go | 7 +++++++ cli/command/registry/login_test.go | 2 +- cli/command/registry/logout.go | 11 +++++++++-- cli/command/registry/search.go | 11 +++++++++-- 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index 36883287ce18..4f1d8783c248 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -43,8 +43,11 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { image.NewPushCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) image.NewImagesCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) registry.NewLoginCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) registry.NewLogoutCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) registry.NewSearchCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) system.NewVersionCommand(dockerCli), diff --git a/cli/command/registry/login.go b/cli/command/registry/login.go index 9ce9ce1cd459..65e90d0cd679 100644 --- a/cli/command/registry/login.go +++ b/cli/command/registry/login.go @@ -32,7 +32,14 @@ type loginOptions struct { } // NewLoginCommand creates a new `docker login` command +// +// Deprecated: Do not import commands directly. They will be removed in a future release. func NewLoginCommand(dockerCLI command.Cli) *cobra.Command { + return newLoginCommand(dockerCLI) +} + +// newLoginCommand creates a new `docker login` command +func newLoginCommand(dockerCLI command.Cli) *cobra.Command { var opts loginOptions cmd := &cobra.Command{ diff --git a/cli/command/registry/login_test.go b/cli/command/registry/login_test.go index d112f08e88e3..7e6ca2ec1c4f 100644 --- a/cli/command/registry/login_test.go +++ b/cli/command/registry/login_test.go @@ -584,7 +584,7 @@ func TestLoginValidateFlags(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - cmd := NewLoginCommand(test.NewFakeCli(&fakeClient{})) + cmd := newLoginCommand(test.NewFakeCli(&fakeClient{})) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs(tc.args) diff --git a/cli/command/registry/logout.go b/cli/command/registry/logout.go index 34498871a557..148152bbd0ec 100644 --- a/cli/command/registry/logout.go +++ b/cli/command/registry/logout.go @@ -13,7 +13,14 @@ import ( ) // NewLogoutCommand creates a new `docker logout` command -func NewLogoutCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewLogoutCommand(dockerCLI command.Cli) *cobra.Command { + return newLogoutCommand(dockerCLI) +} + +// newLogoutCommand creates a new `docker logout` command +func newLogoutCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "logout [SERVER]", Short: "Log out from a registry", @@ -24,7 +31,7 @@ func NewLogoutCommand(dockerCli command.Cli) *cobra.Command { if len(args) > 0 { serverAddress = args[0] } - return runLogout(cmd.Context(), dockerCli, serverAddress) + return runLogout(cmd.Context(), dockerCLI, serverAddress) }, Annotations: map[string]string{ "category-top": "9", diff --git a/cli/command/registry/search.go b/cli/command/registry/search.go index 178a400ddb22..b5e1dcf22082 100644 --- a/cli/command/registry/search.go +++ b/cli/command/registry/search.go @@ -22,7 +22,14 @@ type searchOptions struct { } // NewSearchCommand creates a new `docker search` command -func NewSearchCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewSearchCommand(dockerCLI command.Cli) *cobra.Command { + return newSearchCommand(dockerCLI) +} + +// newSearchCommand creates a new `docker search` command +func newSearchCommand(dockerCLI command.Cli) *cobra.Command { options := searchOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ @@ -31,7 +38,7 @@ func NewSearchCommand(dockerCli command.Cli) *cobra.Command { Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { options.term = args[0] - return runSearch(cmd.Context(), dockerCli, options) + return runSearch(cmd.Context(), dockerCLI, options) }, Annotations: map[string]string{ "category-top": "10", From 733762af0ad86ba0bf70ea06504fb7a5a1441877 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:59:30 +0200 Subject: [PATCH 076/193] Unexport swarm commands This patch deprecates exported swarm commands and moves the implementation details to an unexported function. Commands that are affected include: - swarm.NewSwarmCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit bf39340294eecc19a69ff1e9951e02eefa9c4363) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 1 + cli/command/swarm/cmd.go | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index 4f1d8783c248..d7d619efb696 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -89,6 +89,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { service.NewServiceCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) stack.NewStackCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) swarm.NewSwarmCommand(dockerCli), // legacy commands may be hidden diff --git a/cli/command/swarm/cmd.go b/cli/command/swarm/cmd.go index e78e33d003ec..abce56982ff3 100644 --- a/cli/command/swarm/cmd.go +++ b/cli/command/swarm/cmd.go @@ -8,26 +8,33 @@ import ( ) // NewSwarmCommand returns a cobra command for `swarm` subcommands -func NewSwarmCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewSwarmCommand(dockerCLI command.Cli) *cobra.Command { + return newSwarmCommand(dockerCLI) +} + +// newSwarmCommand returns a cobra command for `swarm` subcommands +func newSwarmCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "swarm", Short: "Manage Swarm", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), Annotations: map[string]string{ "version": "1.24", "swarm": "", // swarm command itself does not require swarm to be enabled (so swarm init and join is always available on API 1.24 and up) }, } cmd.AddCommand( - newInitCommand(dockerCli), - newJoinCommand(dockerCli), - newJoinTokenCommand(dockerCli), - newUnlockKeyCommand(dockerCli), - newUpdateCommand(dockerCli), - newLeaveCommand(dockerCli), - newUnlockCommand(dockerCli), - newCACommand(dockerCli), + newInitCommand(dockerCLI), + newJoinCommand(dockerCLI), + newJoinTokenCommand(dockerCLI), + newUnlockKeyCommand(dockerCLI), + newUpdateCommand(dockerCLI), + newLeaveCommand(dockerCLI), + newUnlockCommand(dockerCLI), + newCACommand(dockerCLI), ) return cmd } From ae3ee9b47bce5b84e0598ddcfe9bee7bb404f162 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:03:57 +0200 Subject: [PATCH 077/193] Unexport plugin commands This patch deprecates exported plugin commands and moves the implementation details to an unexported function. Commands that are affected include: - plugin.NewPluginCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit c6b7268932773346bb9e4eba25673b2064eeaffb) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 1 + cli/command/plugin/cmd.go | 31 +++++++++++++++++++------------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index d7d619efb696..02adcf393ca1 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -71,6 +71,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { manifest.NewManifestCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) network.NewNetworkCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) plugin.NewPluginCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) system.NewSystemCommand(dockerCli), diff --git a/cli/command/plugin/cmd.go b/cli/command/plugin/cmd.go index 2e79ab1d49b5..a72890cebcaa 100644 --- a/cli/command/plugin/cmd.go +++ b/cli/command/plugin/cmd.go @@ -7,26 +7,33 @@ import ( ) // NewPluginCommand returns a cobra command for `plugin` subcommands -func NewPluginCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewPluginCommand(dockerCLI command.Cli) *cobra.Command { + return newPluginCommand(dockerCLI) +} + +// newPluginCommand returns a cobra command for `plugin` subcommands +func newPluginCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "plugin", Short: "Manage plugins", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), Annotations: map[string]string{"version": "1.25"}, } cmd.AddCommand( - newDisableCommand(dockerCli), - newEnableCommand(dockerCli), - newInspectCommand(dockerCli), - newInstallCommand(dockerCli), - newListCommand(dockerCli), - newRemoveCommand(dockerCli), - newSetCommand(dockerCli), - newPushCommand(dockerCli), - newCreateCommand(dockerCli), - newUpgradeCommand(dockerCli), + newDisableCommand(dockerCLI), + newEnableCommand(dockerCLI), + newInspectCommand(dockerCLI), + newInstallCommand(dockerCLI), + newListCommand(dockerCLI), + newRemoveCommand(dockerCLI), + newSetCommand(dockerCLI), + newPushCommand(dockerCLI), + newCreateCommand(dockerCLI), + newUpgradeCommand(dockerCLI), ) return cmd } From ce242b0ee8a3ae7758744082a06bcb8348fdc8c6 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:07:45 +0200 Subject: [PATCH 078/193] Unexport trust commands This patch deprecates exported trust commands and moves the implementation details to an unexported function. Commands that are affected include: - trust.NewTrustCommand Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit bd8e3e444068710c1c33db7e35155dbe3dc00b54) Signed-off-by: Sebastiaan van Stijn --- cli/command/commands/commands.go | 1 + cli/command/trust/cmd.go | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index 02adcf393ca1..6e1d96b66564 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -75,6 +75,7 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { plugin.NewPluginCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) system.NewSystemCommand(dockerCli), + //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) trust.NewTrustCommand(dockerCli), //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) volume.NewVolumeCommand(dockerCli), diff --git a/cli/command/trust/cmd.go b/cli/command/trust/cmd.go index bb6ceace042b..cc0cd6769ffb 100644 --- a/cli/command/trust/cmd.go +++ b/cli/command/trust/cmd.go @@ -7,19 +7,25 @@ import ( ) // NewTrustCommand returns a cobra command for `trust` subcommands -func NewTrustCommand(dockerCli command.Cli) *cobra.Command { +// +// Deprecated: Do not import commands directly. They will be removed in a future release. +func NewTrustCommand(dockerCLI command.Cli) *cobra.Command { + return newTrustCommand(dockerCLI) +} + +func newTrustCommand(dockerCLI command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "trust", Short: "Manage trust on Docker images", Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), + RunE: command.ShowHelp(dockerCLI.Err()), } cmd.AddCommand( - newRevokeCommand(dockerCli), - newSignCommand(dockerCli), - newTrustKeyCommand(dockerCli), - newTrustSignerCommand(dockerCli), - newInspectCommand(dockerCli), + newRevokeCommand(dockerCLI), + newSignCommand(dockerCLI), + newTrustKeyCommand(dockerCLI), + newTrustSignerCommand(dockerCLI), + newInspectCommand(dockerCLI), ) return cmd } From 9e08776681c5506eca64d404ae87d27bde18d3e5 Mon Sep 17 00:00:00 2001 From: Michael Tews Date: Wed, 23 Jul 2025 08:31:48 +0200 Subject: [PATCH 079/193] refactor(cli/compose/loader): extract ParseVolume() to its own package Moves ParseVolume() to a new internal package to remove the dependency on cli/compose/loader in cli/command/container/opts.go refactor to keep types isolated - rename the package to "volumespec" to reuse the name of the package as part of the name (parsevolume.ParseVolume() -> volumespec.Parse()) - move the related compose types to the internal package as well, and rename them to be more generic (not associated with "compose"); - ServiceVolumeConfig -> VolumeConfig - ServiceVolumeBind -> BindOpts - ServiceVolumeVolume -> VolumeOpts - ServiceVolumeImage -> ImageOpts - ServiceVolumeTmpfs -> TmpFsOpts - ServiceVolumeCluster -> ClusterOpts - alias the internal types inside cli/compose/types to keep backward compatibility (for any external consumers); even though the implementation is internal, Go allows aliasing types to use them externally. Signed-off-by: Michael Tews Signed-off-by: Sebastiaan van Stijn (cherry picked from commit ef7fd8bb678d59b29e8f69b284e7e292efdff893) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/opts.go | 4 +- cli/compose/loader/loader.go | 10 ++- cli/compose/types/types.go | 34 +++------- internal/volumespec/types.go | 40 +++++++++++ .../volumespec/volumespec.go | 17 +++-- .../volumespec/volumespec_test.go | 67 +++++++++---------- 6 files changed, 100 insertions(+), 72 deletions(-) create mode 100644 internal/volumespec/types.go rename cli/compose/loader/volume.go => internal/volumespec/volumespec.go (82%) rename cli/compose/loader/volume_test.go => internal/volumespec/volumespec_test.go (77%) diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index 16b9176db9bc..5ff39c4ee03f 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -12,8 +12,8 @@ import ( "strings" "time" - "github.com/docker/cli/cli/compose/loader" "github.com/docker/cli/internal/lazyregexp" + "github.com/docker/cli/internal/volumespec" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/container" mounttypes "github.com/docker/docker/api/types/mount" @@ -374,7 +374,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con volumes := copts.volumes.GetMap() // add any bind targets to the list of container volumes for bind := range copts.volumes.GetMap() { - parsed, err := loader.ParseVolume(bind) + parsed, err := volumespec.Parse(bind) if err != nil { return nil, err } diff --git a/cli/compose/loader/loader.go b/cli/compose/loader/loader.go index b2673394b5bb..839a258f0b6e 100644 --- a/cli/compose/loader/loader.go +++ b/cli/compose/loader/loader.go @@ -17,6 +17,7 @@ import ( "github.com/docker/cli/cli/compose/schema" "github.com/docker/cli/cli/compose/template" "github.com/docker/cli/cli/compose/types" + "github.com/docker/cli/internal/volumespec" "github.com/docker/cli/opts" "github.com/docker/cli/opts/swarmopts" "github.com/docker/docker/api/types/versions" @@ -41,6 +42,13 @@ type Options struct { discardEnvFiles bool } +// ParseVolume parses a volume spec without any knowledge of the target platform. +// +// This function is unused, but kept for backward-compatibility for external users. +func ParseVolume(spec string) (types.ServiceVolumeConfig, error) { + return volumespec.Parse(spec) +} + // WithDiscardEnvFiles sets the Options to discard the `env_file` section after resolving to // the `environment` section func WithDiscardEnvFiles(options *Options) { @@ -756,7 +764,7 @@ var transformBuildConfig TransformerFunc = func(data any) (any, error) { var transformServiceVolumeConfig TransformerFunc = func(data any) (any, error) { switch value := data.(type) { case string: - return ParseVolume(value) + return volumespec.Parse(value) case map[string]any: return data, nil default: diff --git a/cli/compose/types/types.go b/cli/compose/types/types.go index 0804388a57ce..9d0c11b409bb 100644 --- a/cli/compose/types/types.go +++ b/cli/compose/types/types.go @@ -8,6 +8,8 @@ import ( "fmt" "strconv" "time" + + "github.com/docker/cli/internal/volumespec" ) // UnsupportedProperties not yet supported by this implementation of the compose file @@ -390,43 +392,23 @@ type ServicePortConfig struct { } // ServiceVolumeConfig are references to a volume used by a service -type ServiceVolumeConfig struct { - Type string `yaml:",omitempty" json:"type,omitempty"` - Source string `yaml:",omitempty" json:"source,omitempty"` - Target string `yaml:",omitempty" json:"target,omitempty"` - ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"` - Consistency string `yaml:",omitempty" json:"consistency,omitempty"` - Bind *ServiceVolumeBind `yaml:",omitempty" json:"bind,omitempty"` - Volume *ServiceVolumeVolume `yaml:",omitempty" json:"volume,omitempty"` - Image *ServiceVolumeImage `yaml:",omitempty" json:"image,omitempty"` - Tmpfs *ServiceVolumeTmpfs `yaml:",omitempty" json:"tmpfs,omitempty"` - Cluster *ServiceVolumeCluster `yaml:",omitempty" json:"cluster,omitempty"` -} +type ServiceVolumeConfig = volumespec.VolumeConfig // ServiceVolumeBind are options for a service volume of type bind -type ServiceVolumeBind struct { - Propagation string `yaml:",omitempty" json:"propagation,omitempty"` -} +type ServiceVolumeBind = volumespec.BindOpts // ServiceVolumeVolume are options for a service volume of type volume -type ServiceVolumeVolume struct { - NoCopy bool `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"` - Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"` -} +type ServiceVolumeVolume = volumespec.VolumeOpts // ServiceVolumeImage are options for a service volume of type image -type ServiceVolumeImage struct { - Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"` -} +type ServiceVolumeImage = volumespec.ImageOpts // ServiceVolumeTmpfs are options for a service volume of type tmpfs -type ServiceVolumeTmpfs struct { - Size int64 `yaml:",omitempty" json:"size,omitempty"` -} +type ServiceVolumeTmpfs = volumespec.TmpFsOpts // ServiceVolumeCluster are options for a service volume of type cluster. // Deliberately left blank for future options, but unused now. -type ServiceVolumeCluster struct{} +type ServiceVolumeCluster = volumespec.ClusterOpts // FileReferenceConfig for a reference to a swarm file object type FileReferenceConfig struct { diff --git a/internal/volumespec/types.go b/internal/volumespec/types.go new file mode 100644 index 000000000000..7eb9a5006ef0 --- /dev/null +++ b/internal/volumespec/types.go @@ -0,0 +1,40 @@ +package volumespec + +// VolumeConfig are references to a volume used by a service +type VolumeConfig struct { + Type string `yaml:",omitempty" json:"type,omitempty"` + Source string `yaml:",omitempty" json:"source,omitempty"` + Target string `yaml:",omitempty" json:"target,omitempty"` + ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"` + Consistency string `yaml:",omitempty" json:"consistency,omitempty"` + Bind *BindOpts `yaml:",omitempty" json:"bind,omitempty"` + Volume *VolumeOpts `yaml:",omitempty" json:"volume,omitempty"` + Image *ImageOpts `yaml:",omitempty" json:"image,omitempty"` + Tmpfs *TmpFsOpts `yaml:",omitempty" json:"tmpfs,omitempty"` + Cluster *ClusterOpts `yaml:",omitempty" json:"cluster,omitempty"` +} + +// BindOpts are options for a service volume of type bind +type BindOpts struct { + Propagation string `yaml:",omitempty" json:"propagation,omitempty"` +} + +// VolumeOpts are options for a service volume of type volume +type VolumeOpts struct { + NoCopy bool `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"` + Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"` +} + +// ImageOpts are options for a service volume of type image +type ImageOpts struct { + Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"` +} + +// TmpFsOpts are options for a service volume of type tmpfs +type TmpFsOpts struct { + Size int64 `yaml:",omitempty" json:"size,omitempty"` +} + +// ClusterOpts are options for a service volume of type cluster. +// Deliberately left blank for future options, but unused now. +type ClusterOpts struct{} diff --git a/cli/compose/loader/volume.go b/internal/volumespec/volumespec.go similarity index 82% rename from cli/compose/loader/volume.go rename to internal/volumespec/volumespec.go index f043f4aa57ff..0ae4f31c8b95 100644 --- a/cli/compose/loader/volume.go +++ b/internal/volumespec/volumespec.go @@ -1,20 +1,19 @@ -package loader +package volumespec import ( "strings" "unicode" "unicode/utf8" - "github.com/docker/cli/cli/compose/types" "github.com/docker/docker/api/types/mount" "github.com/pkg/errors" ) const endOfSpec = rune(0) -// ParseVolume parses a volume spec without any knowledge of the target platform -func ParseVolume(spec string) (types.ServiceVolumeConfig, error) { - volume := types.ServiceVolumeConfig{} +// Parse parses a volume spec without any knowledge of the target platform +func Parse(spec string) (VolumeConfig, error) { + volume := VolumeConfig{} switch len(spec) { case 0: @@ -49,7 +48,7 @@ func isWindowsDrive(buffer []rune, char rune) bool { return char == ':' && len(buffer) == 1 && unicode.IsLetter(buffer[0]) } -func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolumeConfig) error { +func populateFieldFromBuffer(char rune, buffer []rune, volume *VolumeConfig) error { strBuffer := string(buffer) switch { case len(buffer) == 0: @@ -74,10 +73,10 @@ func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolu case "rw": volume.ReadOnly = false case "nocopy": - volume.Volume = &types.ServiceVolumeVolume{NoCopy: true} + volume.Volume = &VolumeOpts{NoCopy: true} default: if isBindOption(option) { - volume.Bind = &types.ServiceVolumeBind{Propagation: option} + volume.Bind = &BindOpts{Propagation: option} } // ignore unknown options } @@ -94,7 +93,7 @@ func isBindOption(option string) bool { return false } -func populateType(volume *types.ServiceVolumeConfig) { +func populateType(volume *VolumeConfig) { switch { // Anonymous volume case volume.Source == "": diff --git a/cli/compose/loader/volume_test.go b/internal/volumespec/volumespec_test.go similarity index 77% rename from cli/compose/loader/volume_test.go rename to internal/volumespec/volumespec_test.go index 54dc200fe8f6..b4d356fffaf7 100644 --- a/cli/compose/loader/volume_test.go +++ b/internal/volumespec/volumespec_test.go @@ -1,18 +1,17 @@ -package loader +package volumespec import ( "fmt" "testing" - "github.com/docker/cli/cli/compose/types" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) func TestParseVolumeAnonymousVolume(t *testing.T) { for _, path := range []string{"/path", "/path/foo"} { - volume, err := ParseVolume(path) - expected := types.ServiceVolumeConfig{Type: "volume", Target: path} + volume, err := Parse(path) + expected := VolumeConfig{Type: "volume", Target: path} assert.NilError(t, err) assert.Check(t, is.DeepEqual(expected, volume)) } @@ -20,22 +19,22 @@ func TestParseVolumeAnonymousVolume(t *testing.T) { func TestParseVolumeAnonymousVolumeWindows(t *testing.T) { for _, path := range []string{"C:\\path", "Z:\\path\\foo"} { - volume, err := ParseVolume(path) - expected := types.ServiceVolumeConfig{Type: "volume", Target: path} + volume, err := Parse(path) + expected := VolumeConfig{Type: "volume", Target: path} assert.NilError(t, err) assert.Check(t, is.DeepEqual(expected, volume)) } } func TestParseVolumeTooManyColons(t *testing.T) { - _, err := ParseVolume("/foo:/foo:ro:foo") + _, err := Parse("/foo:/foo:ro:foo") assert.Error(t, err, "invalid spec: /foo:/foo:ro:foo: too many colons") } func TestParseVolumeShortVolumes(t *testing.T) { for _, path := range []string{".", "/a"} { - volume, err := ParseVolume(path) - expected := types.ServiceVolumeConfig{Type: "volume", Target: path} + volume, err := Parse(path) + expected := VolumeConfig{Type: "volume", Target: path} assert.NilError(t, err) assert.Check(t, is.DeepEqual(expected, volume)) } @@ -43,15 +42,15 @@ func TestParseVolumeShortVolumes(t *testing.T) { func TestParseVolumeMissingSource(t *testing.T) { for _, spec := range []string{":foo", "/foo::ro"} { - _, err := ParseVolume(spec) + _, err := Parse(spec) assert.ErrorContains(t, err, "empty section between colons") } } func TestParseVolumeBindMount(t *testing.T) { for _, path := range []string{"./foo", "~/thing", "../other", "/foo", "/home/user"} { - volume, err := ParseVolume(path + ":/target") - expected := types.ServiceVolumeConfig{ + volume, err := Parse(path + ":/target") + expected := VolumeConfig{ Type: "bind", Source: path, Target: "/target", @@ -68,8 +67,8 @@ func TestParseVolumeRelativeBindMountWindows(t *testing.T) { "../other", "D:\\path", "/home/user", } { - volume, err := ParseVolume(path + ":d:\\target") - expected := types.ServiceVolumeConfig{ + volume, err := Parse(path + ":d:\\target") + expected := VolumeConfig{ Type: "bind", Source: path, Target: "d:\\target", @@ -80,42 +79,42 @@ func TestParseVolumeRelativeBindMountWindows(t *testing.T) { } func TestParseVolumeWithBindOptions(t *testing.T) { - volume, err := ParseVolume("/source:/target:slave") - expected := types.ServiceVolumeConfig{ + volume, err := Parse("/source:/target:slave") + expected := VolumeConfig{ Type: "bind", Source: "/source", Target: "/target", - Bind: &types.ServiceVolumeBind{Propagation: "slave"}, + Bind: &BindOpts{Propagation: "slave"}, } assert.NilError(t, err) assert.Check(t, is.DeepEqual(expected, volume)) } func TestParseVolumeWithBindOptionsWindows(t *testing.T) { - volume, err := ParseVolume("C:\\source\\foo:D:\\target:ro,rprivate") - expected := types.ServiceVolumeConfig{ + volume, err := Parse("C:\\source\\foo:D:\\target:ro,rprivate") + expected := VolumeConfig{ Type: "bind", Source: "C:\\source\\foo", Target: "D:\\target", ReadOnly: true, - Bind: &types.ServiceVolumeBind{Propagation: "rprivate"}, + Bind: &BindOpts{Propagation: "rprivate"}, } assert.NilError(t, err) assert.Check(t, is.DeepEqual(expected, volume)) } func TestParseVolumeWithInvalidVolumeOptions(t *testing.T) { - _, err := ParseVolume("name:/target:bogus") + _, err := Parse("name:/target:bogus") assert.NilError(t, err) } func TestParseVolumeWithVolumeOptions(t *testing.T) { - volume, err := ParseVolume("name:/target:nocopy") - expected := types.ServiceVolumeConfig{ + volume, err := Parse("name:/target:nocopy") + expected := VolumeConfig{ Type: "volume", Source: "name", Target: "/target", - Volume: &types.ServiceVolumeVolume{NoCopy: true}, + Volume: &VolumeOpts{NoCopy: true}, } assert.NilError(t, err) assert.Check(t, is.DeepEqual(expected, volume)) @@ -123,8 +122,8 @@ func TestParseVolumeWithVolumeOptions(t *testing.T) { func TestParseVolumeWithReadOnly(t *testing.T) { for _, path := range []string{"./foo", "/home/user"} { - volume, err := ParseVolume(path + ":/target:ro") - expected := types.ServiceVolumeConfig{ + volume, err := Parse(path + ":/target:ro") + expected := VolumeConfig{ Type: "bind", Source: path, Target: "/target", @@ -137,8 +136,8 @@ func TestParseVolumeWithReadOnly(t *testing.T) { func TestParseVolumeWithRW(t *testing.T) { for _, path := range []string{"./foo", "/home/user"} { - volume, err := ParseVolume(path + ":/target:rw") - expected := types.ServiceVolumeConfig{ + volume, err := Parse(path + ":/target:rw") + expected := VolumeConfig{ Type: "bind", Source: path, Target: "/target", @@ -150,9 +149,9 @@ func TestParseVolumeWithRW(t *testing.T) { } func TestParseVolumeWindowsNamedPipe(t *testing.T) { - volume, err := ParseVolume(`\\.\pipe\docker_engine:\\.\pipe\inside`) + volume, err := Parse(`\\.\pipe\docker_engine:\\.\pipe\inside`) assert.NilError(t, err) - expected := types.ServiceVolumeConfig{ + expected := VolumeConfig{ Type: "bind", Source: `\\.\pipe\docker_engine`, Target: `\\.\pipe\inside`, @@ -206,7 +205,7 @@ func TestParseVolumeSplitCases(t *testing.T) { // Cover directories with one-character name {`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}}, } { - parsed, _ := ParseVolume(x.input) + parsed, _ := Parse(x.input) expected := len(x.expected) > 1 msg := fmt.Sprintf("Case %d: %s", casenumber, x.input) @@ -215,16 +214,16 @@ func TestParseVolumeSplitCases(t *testing.T) { } func TestParseVolumeInvalidEmptySpec(t *testing.T) { - _, err := ParseVolume("") + _, err := Parse("") assert.ErrorContains(t, err, "invalid empty volume spec") } func TestParseVolumeInvalidSections(t *testing.T) { - _, err := ParseVolume("/foo::rw") + _, err := Parse("/foo::rw") assert.ErrorContains(t, err, "invalid spec") } func TestParseVolumeWithEmptySource(t *testing.T) { - _, err := ParseVolume(":/vol") + _, err := Parse(":/vol") assert.ErrorContains(t, err, "empty section between colons") } From cb42a72704d432914914d7df214e6662d9633e45 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 20 Aug 2025 17:49:59 +0200 Subject: [PATCH 080/193] cli/command: deprecate prompt utilities that were for internal use - The `DisableInputEcho` and `PromptForInput` utilities were added in c15ade0c647606b769deb009a3c2e508efa71e67 as part of a bug-fix, which was part of v28.x. [There are no (publicly visible) users][1] of either. - The `ErrPromptTerminated` was added in v26.x (originally added in 10bf91a02d2abd4dec90b79bd10b91f6fbb8e05d, later updated in commit 7c722c08d093c118ea727961be9aa0a23c83b733. [It is not used][2] - The `PromptForConfirmation` was added in [moby@280c872] (docker v1.13.0) as part of the `docker prune` subcommands. It was meant for internal use but exported to allow re-using it in the `container`, `image` (etc.) packages. However, a breaking change to its signature was made in 10bf91a02d2abd4dec90b79bd10b91f6fbb8e05d. It currently does [not appear to have any (public) users][2]. This patch deprecates the `ErrPromptTerminated`, `DisableInputEcho`, `PromptForInput`, and `PromptForConfirmation` utilities from the `cli/command` package. The core functionality of these is still available in the `internal/prompt` package, which we may make public at some point, but still needs some refining / decoupling. [moby@280c872]: https://github.com/moby/moby/commit/280c8723667af385e0807a090ddc5cc57c46807e [1]: https://grep.app/search?f.lang=Go®exp=true&q=%5C.%28DisableInputEcho%7CPromptForInput%29%5C%28 [2]: https://grep.app/search?f.lang=Go&q=%5C.ErrPromptTerminated [3]: https://grep.app/search?f.lang=Go&q=.PromptForConfirmation%28 Signed-off-by: Sebastiaan van Stijn --- cli/command/utils.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cli/command/utils.go b/cli/command/utils.go index 5987b537172e..fdedc2a14fb9 100644 --- a/cli/command/utils.go +++ b/cli/command/utils.go @@ -17,12 +17,17 @@ import ( "github.com/pkg/errors" ) +// ErrPromptTerminated is returned if the user terminated the prompt. +// +// Deprecated: this error is for internal use and will be removed in the next release. const ErrPromptTerminated = prompt.ErrTerminated // DisableInputEcho disables input echo on the provided streams.In. // This is useful when the user provides sensitive information like passwords. // The function returns a restore function that should be called to restore the // terminal state. +// +// Deprecated: this function is for internal use and will be removed in the next release. func DisableInputEcho(ins *streams.In) (restore func() error, err error) { return prompt.DisableInputEcho(ins) } @@ -34,6 +39,8 @@ func DisableInputEcho(ins *streams.In) (restore func() error, err error) { // When the prompt returns an error, the caller should propagate the error up // the stack and close the io.Reader used for the prompt which will prevent the // background goroutine from blocking indefinitely. +// +// Deprecated: this function is for internal use and will be removed in the next release. func PromptForInput(ctx context.Context, in io.Reader, out io.Writer, message string) (string, error) { return prompt.ReadInput(ctx, in, out, message) } @@ -48,6 +55,8 @@ func PromptForInput(ctx context.Context, in io.Reader, out io.Writer, message st // When the prompt returns an error, the caller should propagate the error up // the stack and close the io.Reader used for the prompt which will prevent the // background goroutine from blocking indefinitely. +// +// Deprecated: this function is for internal use and will be removed in the next release. func PromptForConfirmation(ctx context.Context, ins io.Reader, outs io.Writer, message string) (bool, error) { return prompt.Confirm(ctx, ins, outs, message) } From 39e121361570a8c32af08305f89c355e18f4ac3f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 20 Aug 2025 22:00:06 +0200 Subject: [PATCH 081/193] e2e/testutils: fix incorrect use of PluginConfigInterface This code was using the type incorrectly; current versions of the API MarshalText ignore this mistake, but the moby/moby/api module produces an error: === Failed === FAIL: e2e/global TestPromptExitCode/plugin_install (0.28s) cli_test.go:203: assertion failed: error is not nil: json: error calling MarshalText for type plugin.CapabilityID: capability "docker.dummy/1.0" cannot contain a dot === FAIL: e2e/global TestPromptExitCode/plugin_upgrade (0.26s) cli_test.go:203: assertion failed: error is not nil: json: error calling MarshalText for type plugin.CapabilityID: capability "docker.dummy/1.0" cannot contain a dot Signed-off-by: Sebastiaan van Stijn --- e2e/testutils/plugins.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/e2e/testutils/plugins.go b/e2e/testutils/plugins.go index 1485f425b1fa..7406798fd764 100644 --- a/e2e/testutils/plugins.go +++ b/e2e/testutils/plugins.go @@ -32,7 +32,11 @@ func SetupPlugin(t *testing.T, ctx context.Context) *fs.Dir { }, Interface: types.PluginConfigInterface{ Socket: "basic.sock", - Types: []types.PluginInterfaceType{{Capability: "docker.dummy/1.0"}}, + Types: []types.PluginInterfaceType{{ + Capability: "dummy", + Prefix: "docker", + Version: "1.0", + }}, }, Entrypoint: []string{"/basic"}, } From ec89d0ba67b365c59894fc135987aff9fb361ac7 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sat, 26 Jul 2025 15:58:06 +0200 Subject: [PATCH 082/193] cli/command/plugin: fix linting issues, and assorted cleanups - fix various unhandled errors - remove some locally defined option-types in favor of option-types defined by the client / api - don't use unkeyed structs in tests, and add docs for some subtests - fix some values in tests that triggered "spellcheck" warnings - inline vars / functions that only had a single use. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit c6f935eba503c9ad7b5c5b42078b910a08ce72d3) Signed-off-by: Sebastiaan van Stijn --- cli/command/plugin/create.go | 2 +- cli/command/plugin/disable.go | 22 ++-- cli/command/plugin/enable.go | 31 ++--- cli/command/plugin/enable_test.go | 2 +- cli/command/plugin/formatter.go | 8 +- cli/command/plugin/formatter_test.go | 116 +++++++++++------- cli/command/plugin/inspect.go | 12 +- cli/command/plugin/inspect_test.go | 8 +- cli/command/plugin/install.go | 4 +- cli/command/plugin/install_test.go | 2 +- cli/command/plugin/list_test.go | 4 +- cli/command/plugin/push.go | 4 +- cli/command/plugin/remove_test.go | 2 +- cli/command/plugin/set.go | 4 +- ...lugin-inspect-single-without-format.golden | 4 +- cli/command/plugin/upgrade.go | 4 +- 16 files changed, 124 insertions(+), 105 deletions(-) diff --git a/cli/command/plugin/create.go b/cli/command/plugin/create.go index 8f408d76f3fa..b10da8cb29de 100644 --- a/cli/command/plugin/create.go +++ b/cli/command/plugin/create.go @@ -40,7 +40,7 @@ func validateConfig(path string) error { return err } -// validateContextDir validates the given dir and returns abs path on success. +// validateContextDir validates the given dir and returns its absolute path on success. func validateContextDir(contextDir string) (string, error) { absContextDir, err := filepath.Abs(contextDir) if err != nil { diff --git a/cli/command/plugin/disable.go b/cli/command/plugin/disable.go index 084b50365d1e..b58990456b90 100644 --- a/cli/command/plugin/disable.go +++ b/cli/command/plugin/disable.go @@ -1,7 +1,6 @@ package plugin import ( - "context" "fmt" "github.com/docker/cli/cli" @@ -10,27 +9,24 @@ import ( "github.com/spf13/cobra" ) -func newDisableCommand(dockerCli command.Cli) *cobra.Command { - var force bool +func newDisableCommand(dockerCLI command.Cli) *cobra.Command { + var opts types.PluginDisableOptions cmd := &cobra.Command{ Use: "disable [OPTIONS] PLUGIN", Short: "Disable a plugin", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return runDisable(cmd.Context(), dockerCli, args[0], force) + name := args[0] + if err := dockerCLI.Client().PluginDisable(cmd.Context(), name, opts); err != nil { + return err + } + _, _ = fmt.Fprintln(dockerCLI.Out(), name) + return nil }, } flags := cmd.Flags() - flags.BoolVarP(&force, "force", "f", false, "Force the disable of an active plugin") + flags.BoolVarP(&opts.Force, "force", "f", false, "Force the disable of an active plugin") return cmd } - -func runDisable(ctx context.Context, dockerCli command.Cli, name string, force bool) error { - if err := dockerCli.Client().PluginDisable(ctx, name, types.PluginDisableOptions{Force: force}); err != nil { - return err - } - fmt.Fprintln(dockerCli.Out(), name) - return nil -} diff --git a/cli/command/plugin/enable.go b/cli/command/plugin/enable.go index e1724fd50cf8..befd553cc8b4 100644 --- a/cli/command/plugin/enable.go +++ b/cli/command/plugin/enable.go @@ -11,38 +11,31 @@ import ( "github.com/spf13/cobra" ) -type enableOpts struct { - timeout int - name string -} - func newEnableCommand(dockerCli command.Cli) *cobra.Command { - var opts enableOpts + var opts types.PluginEnableOptions cmd := &cobra.Command{ Use: "enable [OPTIONS] PLUGIN", Short: "Enable a plugin", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.name = args[0] - return runEnable(cmd.Context(), dockerCli, &opts) + name := args[0] + if err := runEnable(cmd.Context(), dockerCli, name, opts); err != nil { + return err + } + _, _ = fmt.Fprintln(dockerCli.Out(), name) + return nil }, } flags := cmd.Flags() - flags.IntVar(&opts.timeout, "timeout", 30, "HTTP client timeout (in seconds)") + flags.IntVar(&opts.Timeout, "timeout", 30, "HTTP client timeout (in seconds)") return cmd } -func runEnable(ctx context.Context, dockerCli command.Cli, opts *enableOpts) error { - name := opts.name - if opts.timeout < 0 { - return errors.Errorf("negative timeout %d is invalid", opts.timeout) - } - - if err := dockerCli.Client().PluginEnable(ctx, name, types.PluginEnableOptions{Timeout: opts.timeout}); err != nil { - return err +func runEnable(ctx context.Context, dockerCli command.Cli, name string, opts types.PluginEnableOptions) error { + if opts.Timeout < 0 { + return errors.Errorf("negative timeout %d is invalid", opts.Timeout) } - fmt.Fprintln(dockerCli.Out(), name) - return nil + return dockerCli.Client().PluginEnable(ctx, name, opts) } diff --git a/cli/command/plugin/enable_test.go b/cli/command/plugin/enable_test.go index bb0861e6ce20..65a096d8c663 100644 --- a/cli/command/plugin/enable_test.go +++ b/cli/command/plugin/enable_test.go @@ -48,7 +48,7 @@ func TestPluginEnableErrors(t *testing.T) { })) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.NilError(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) diff --git a/cli/command/plugin/formatter.go b/cli/command/plugin/formatter.go index 27c24b52cdeb..d468c07e0a09 100644 --- a/cli/command/plugin/formatter.go +++ b/cli/command/plugin/formatter.go @@ -12,6 +12,12 @@ const ( enabledHeader = "ENABLED" pluginIDHeader = "ID" + + rawFormat = `plugin_id: {{.ID}} +name: {{.Name}} +description: {{.Description}} +enabled: {{.Enabled}} +` ) // NewFormat returns a Format for rendering using a plugin Context @@ -26,7 +32,7 @@ func NewFormat(source string, quiet bool) formatter.Format { if quiet { return `plugin_id: {{.ID}}` } - return `plugin_id: {{.ID}}\nname: {{.Name}}\ndescription: {{.Description}}\nenabled: {{.Enabled}}\n` + return rawFormat } return formatter.Format(source) } diff --git a/cli/command/plugin/formatter_test.go b/cli/command/plugin/formatter_test.go index 63f1ce29e983..513cde4c4828 100644 --- a/cli/command/plugin/formatter_test.go +++ b/cli/command/plugin/formatter_test.go @@ -19,85 +19,106 @@ import ( func TestPluginContext(t *testing.T) { pluginID := test.RandomID() - var ctx pluginContext - cases := []struct { + var pCtx pluginContext + tests := []struct { pluginCtx pluginContext expValue string call func() string }{ - {pluginContext{ - p: types.Plugin{ID: pluginID}, - trunc: false, - }, pluginID, ctx.ID}, - {pluginContext{ - p: types.Plugin{ID: pluginID}, - trunc: true, - }, formatter.TruncateID(pluginID), ctx.ID}, - {pluginContext{ - p: types.Plugin{Name: "plugin_name"}, - }, "plugin_name", ctx.Name}, - {pluginContext{ - p: types.Plugin{Config: types.PluginConfig{Description: "plugin_description"}}, - }, "plugin_description", ctx.Description}, + { + pluginCtx: pluginContext{ + p: types.Plugin{ID: pluginID}, + trunc: false, + }, + expValue: pluginID, + call: pCtx.ID, + }, + { + pluginCtx: pluginContext{ + p: types.Plugin{ID: pluginID}, + trunc: true, + }, + expValue: formatter.TruncateID(pluginID), + call: pCtx.ID, + }, + { + pluginCtx: pluginContext{ + p: types.Plugin{Name: "plugin_name"}, + }, + expValue: "plugin_name", + call: pCtx.Name, + }, + { + pluginCtx: pluginContext{ + p: types.Plugin{Config: types.PluginConfig{Description: "plugin_description"}}, + }, + expValue: "plugin_description", + call: pCtx.Description, + }, } - for _, c := range cases { - ctx = c.pluginCtx - v := c.call() + for _, tc := range tests { + pCtx = tc.pluginCtx + v := tc.call() if strings.Contains(v, ",") { - test.CompareMultipleValues(t, v, c.expValue) - } else if v != c.expValue { - t.Fatalf("Expected %s, was %s\n", c.expValue, v) + test.CompareMultipleValues(t, v, tc.expValue) + } else if v != tc.expValue { + t.Fatalf("Expected %s, was %s\n", tc.expValue, v) } } } func TestPluginContextWrite(t *testing.T) { - cases := []struct { + tests := []struct { + doc string context formatter.Context expected string }{ - // Errors { - formatter.Context{Format: "{{InvalidFunction}}"}, - `template parsing error: template: :1: function "InvalidFunction" not defined`, + doc: "invalid function", + context: formatter.Context{Format: "{{InvalidFunction}}"}, + expected: `template parsing error: template: :1: function "InvalidFunction" not defined`, }, { - formatter.Context{Format: "{{nil}}"}, - `template parsing error: template: :1:2: executing "" at : nil is not a command`, + doc: "nil template", + context: formatter.Context{Format: "{{nil}}"}, + expected: `template parsing error: template: :1:2: executing "" at : nil is not a command`, }, - // Table format { - formatter.Context{Format: NewFormat("table", false)}, - `ID NAME DESCRIPTION ENABLED + doc: "table format", + context: formatter.Context{Format: NewFormat("table", false)}, + expected: `ID NAME DESCRIPTION ENABLED pluginID1 foobar_baz description 1 true pluginID2 foobar_bar description 2 false `, }, { - formatter.Context{Format: NewFormat("table", true)}, - `pluginID1 + doc: "table format, quiet", + context: formatter.Context{Format: NewFormat("table", true)}, + expected: `pluginID1 pluginID2 `, }, { - formatter.Context{Format: NewFormat("table {{.Name}}", false)}, - `NAME + doc: "table format name col", + context: formatter.Context{Format: NewFormat("table {{.Name}}", false)}, + expected: `NAME foobar_baz foobar_bar `, }, { - formatter.Context{Format: NewFormat("table {{.Name}}", true)}, - `NAME + doc: "table format name col, quiet", + context: formatter.Context{Format: NewFormat("table {{.Name}}", true)}, + expected: `NAME foobar_baz foobar_bar `, }, - // Raw Format { - formatter.Context{Format: NewFormat("raw", false)}, - `plugin_id: pluginID1 + doc: "raw format", + context: formatter.Context{Format: NewFormat("raw", false)}, + expected: `plugin_id: pluginID1 name: foobar_baz description: description 1 enabled: true @@ -110,15 +131,16 @@ enabled: false `, }, { - formatter.Context{Format: NewFormat("raw", true)}, - `plugin_id: pluginID1 + doc: "raw format, quiet", + context: formatter.Context{Format: NewFormat("raw", true)}, + expected: `plugin_id: pluginID1 plugin_id: pluginID2 `, }, - // Custom Format { - formatter.Context{Format: NewFormat("{{.Name}}", false)}, - `foobar_baz + doc: "custom format", + context: formatter.Context{Format: NewFormat("{{.Name}}", false)}, + expected: `foobar_baz foobar_bar `, }, @@ -129,8 +151,8 @@ foobar_bar {ID: "pluginID2", Name: "foobar_bar", Config: types.PluginConfig{Description: "description 2"}, Enabled: false}, } - for _, tc := range cases { - t.Run(string(tc.context.Format), func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.doc, func(t *testing.T) { var out bytes.Buffer tc.context.Output = &out diff --git a/cli/command/plugin/inspect.go b/cli/command/plugin/inspect.go index 9b6a453c9336..289fe94f5e24 100644 --- a/cli/command/plugin/inspect.go +++ b/cli/command/plugin/inspect.go @@ -36,11 +36,9 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error { - client := dockerCli.Client() - getRef := func(ref string) (any, []byte, error) { - return client.PluginInspectWithRaw(ctx, ref) - } - - return inspect.Inspect(dockerCli.Out(), opts.pluginNames, opts.format, getRef) +func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error { + apiClient := dockerCLI.Client() + return inspect.Inspect(dockerCLI.Out(), opts.pluginNames, opts.format, func(ref string) (any, []byte, error) { + return apiClient.PluginInspectWithRaw(ctx, ref) + }) } diff --git a/cli/command/plugin/inspect_test.go b/cli/command/plugin/inspect_test.go index 414ff479a0c2..296408eed5bb 100644 --- a/cli/command/plugin/inspect_test.go +++ b/cli/command/plugin/inspect_test.go @@ -21,14 +21,14 @@ var pluginFoo = &types.Plugin{ Documentation: "plugin foo documentation", Entrypoint: []string{"/foo"}, Interface: types.PluginConfigInterface{ - Socket: "pluginfoo.sock", + Socket: "plugin-foo.sock", }, Linux: types.PluginConfigLinux{ Capabilities: []string{"CAP_SYS_ADMIN"}, }, WorkDir: "workdir-foo", Rootfs: &types.PluginConfigRootfs{ - DiffIds: []string{"sha256:8603eedd4ea52cebb2f22b45405a3dc8f78ba3e31bf18f27b4547a9ff930e0bd"}, + DiffIds: []string{"sha256:deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}, Type: "layers", }, }, @@ -71,7 +71,7 @@ func TestInspectErrors(t *testing.T) { cmd := newInspectCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.NilError(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) @@ -142,7 +142,7 @@ func TestInspect(t *testing.T) { cmd := newInspectCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.NilError(t, cmd.Flags().Set(key, value)) } assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), tc.golden) diff --git a/cli/command/plugin/install.go b/cli/command/plugin/install.go index 31f123ddfeac..e232fdf0034a 100644 --- a/cli/command/plugin/install.go +++ b/cli/command/plugin/install.go @@ -125,7 +125,9 @@ func runInstall(ctx context.Context, dockerCLI command.Cli, opts pluginOptions) } return err } - defer responseBody.Close() + defer func() { + _ = responseBody.Close() + }() if err := jsonstream.Display(ctx, responseBody, dockerCLI.Out()); err != nil { return err } diff --git a/cli/command/plugin/install_test.go b/cli/command/plugin/install_test.go index b59ed98fcbee..f66082d9302e 100644 --- a/cli/command/plugin/install_test.go +++ b/cli/command/plugin/install_test.go @@ -32,7 +32,7 @@ func TestInstallErrors(t *testing.T) { }, { description: "invalid plugin name", - args: []string{"UPPERCASE_REPONAME"}, + args: []string{"UPPERCASE_REPO_NAME"}, expectedError: "invalid", }, { diff --git a/cli/command/plugin/list_test.go b/cli/command/plugin/list_test.go index 91665da654f1..798db7fb9fb6 100644 --- a/cli/command/plugin/list_test.go +++ b/cli/command/plugin/list_test.go @@ -51,7 +51,7 @@ func TestListErrors(t *testing.T) { cmd := newListCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.NilError(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) @@ -170,7 +170,7 @@ func TestList(t *testing.T) { cmd := newListCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.NilError(t, cmd.Flags().Set(key, value)) } assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), tc.golden) diff --git a/cli/command/plugin/push.go b/cli/command/plugin/push.go index f70e36910c8d..6598363f0b50 100644 --- a/cli/command/plugin/push.go +++ b/cli/command/plugin/push.go @@ -60,7 +60,9 @@ func runPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error if err != nil { return err } - defer responseBody.Close() + defer func() { + _ = responseBody.Close() + }() if !opts.untrusted { repoInfo := &trust.RepositoryInfo{ diff --git a/cli/command/plugin/remove_test.go b/cli/command/plugin/remove_test.go index de40a28ade52..f38e22b40a4c 100644 --- a/cli/command/plugin/remove_test.go +++ b/cli/command/plugin/remove_test.go @@ -64,7 +64,7 @@ func TestRemoveWithForceOption(t *testing.T) { }) cmd := newRemoveCommand(cli) cmd.SetArgs([]string{"plugin-foo"}) - cmd.Flags().Set("force", "true") + assert.NilError(t, cmd.Flags().Set("force", "true")) assert.NilError(t, cmd.Execute()) assert.Check(t, force) assert.Check(t, is.Equal("plugin-foo\n", cli.OutBuffer().String())) diff --git a/cli/command/plugin/set.go b/cli/command/plugin/set.go index 64442097b3aa..2d1eecc0ad0b 100644 --- a/cli/command/plugin/set.go +++ b/cli/command/plugin/set.go @@ -7,7 +7,7 @@ import ( ) func newSetCommand(dockerCli command.Cli) *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "set PLUGIN KEY=VALUE [KEY=VALUE...]", Short: "Change settings for a plugin", Args: cli.RequiresMinArgs(2), @@ -15,6 +15,4 @@ func newSetCommand(dockerCli command.Cli) *cobra.Command { return dockerCli.Client().PluginSet(cmd.Context(), args[0], args[1:]) }, } - - return cmd } diff --git a/cli/command/plugin/testdata/plugin-inspect-single-without-format.golden b/cli/command/plugin/testdata/plugin-inspect-single-without-format.golden index 65c8d39ce995..be510f40349b 100644 --- a/cli/command/plugin/testdata/plugin-inspect-single-without-format.golden +++ b/cli/command/plugin/testdata/plugin-inspect-single-without-format.golden @@ -15,7 +15,7 @@ ], "Env": null, "Interface": { - "Socket": "pluginfoo.sock", + "Socket": "plugin-foo.sock", "Types": null }, "IpcHost": false, @@ -36,7 +36,7 @@ "WorkDir": "workdir-foo", "rootfs": { "diff_ids": [ - "sha256:8603eedd4ea52cebb2f22b45405a3dc8f78ba3e31bf18f27b4547a9ff930e0bd" + "sha256:deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" ], "type": "layers" } diff --git a/cli/command/plugin/upgrade.go b/cli/command/plugin/upgrade.go index 5489232d9a2a..7d4548d428f1 100644 --- a/cli/command/plugin/upgrade.go +++ b/cli/command/plugin/upgrade.go @@ -85,7 +85,9 @@ func runUpgrade(ctx context.Context, dockerCLI command.Cli, opts pluginOptions) } return err } - defer responseBody.Close() + defer func() { + _ = responseBody.Close() + }() if err := jsonstream.Display(ctx, responseBody, dockerCLI.Out()); err != nil { return err } From 34a75fb6f4d6154803d3e26fb6f691c80a23ce77 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 16 Jul 2025 13:39:09 +0200 Subject: [PATCH 083/193] cli/command/container: deprecate NewDiffFormat It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 907507e22ade8bdcec81922efe96c240c635f178) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/diff.go | 2 +- cli/command/container/formatter_diff.go | 7 +++++++ cli/command/container/formatter_diff_test.go | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cli/command/container/diff.go b/cli/command/container/diff.go index 26fdacc9c695..d9b4954c564a 100644 --- a/cli/command/container/diff.go +++ b/cli/command/container/diff.go @@ -39,7 +39,7 @@ func runDiff(ctx context.Context, dockerCLI command.Cli, containerID string) err } diffCtx := formatter.Context{ Output: dockerCLI.Out(), - Format: NewDiffFormat("{{.Type}} {{.Path}}"), + Format: newDiffFormat("{{.Type}} {{.Path}}"), } return DiffFormatWrite(diffCtx, changes) } diff --git a/cli/command/container/formatter_diff.go b/cli/command/container/formatter_diff.go index 822e1eef51b0..1bf5e78f6178 100644 --- a/cli/command/container/formatter_diff.go +++ b/cli/command/container/formatter_diff.go @@ -13,7 +13,14 @@ const ( ) // NewDiffFormat returns a format for use with a diff Context +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewDiffFormat(source string) formatter.Format { + return newDiffFormat(source) +} + +// newDiffFormat returns a format for use with a diff [formatter.Context]. +func newDiffFormat(source string) formatter.Format { if source == formatter.TableFormatKey { return defaultDiffTableFormat } diff --git a/cli/command/container/formatter_diff_test.go b/cli/command/container/formatter_diff_test.go index e06117e84a2e..0d9ba20d3345 100644 --- a/cli/command/container/formatter_diff_test.go +++ b/cli/command/container/formatter_diff_test.go @@ -16,7 +16,7 @@ func TestDiffContextFormatWrite(t *testing.T) { expected string }{ { - formatter.Context{Format: NewDiffFormat("table")}, + formatter.Context{Format: newDiffFormat("table")}, `CHANGE TYPE PATH C /var/log/app.log A /usr/app/app.js @@ -24,7 +24,7 @@ D /usr/app/old_app.js `, }, { - formatter.Context{Format: NewDiffFormat("table {{.Path}}")}, + formatter.Context{Format: newDiffFormat("table {{.Path}}")}, `PATH /var/log/app.log /usr/app/app.js @@ -32,7 +32,7 @@ D /usr/app/old_app.js `, }, { - formatter.Context{Format: NewDiffFormat("{{.Type}}: {{.Path}}")}, + formatter.Context{Format: newDiffFormat("{{.Type}}: {{.Path}}")}, `C: /var/log/app.log A: /usr/app/app.js D: /usr/app/old_app.js From 85a5e3da36eb5d78c9e83755cdd5ff45bc0ef0a4 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 16 Jul 2025 13:37:21 +0200 Subject: [PATCH 084/193] cli/command/container: DiffFormatWrite: remove intermediate var Also rename "ctx" argument; we shouldn't use this as name for things that are not a context.Context. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 239b727834e14d9ef39ed4ee90e9056fca8a3cb3) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/formatter_diff.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cli/command/container/formatter_diff.go b/cli/command/container/formatter_diff.go index 1bf5e78f6178..45c45da9b3ad 100644 --- a/cli/command/container/formatter_diff.go +++ b/cli/command/container/formatter_diff.go @@ -28,16 +28,15 @@ func newDiffFormat(source string) formatter.Format { } // DiffFormatWrite writes formatted diff using the Context -func DiffFormatWrite(ctx formatter.Context, changes []container.FilesystemChange) error { - render := func(format func(subContext formatter.SubContext) error) error { +func DiffFormatWrite(fmtCtx formatter.Context, changes []container.FilesystemChange) error { + return fmtCtx.Write(newDiffContext(), func(format func(subContext formatter.SubContext) error) error { for _, change := range changes { if err := format(&diffContext{c: change}); err != nil { return err } } return nil - } - return ctx.Write(newDiffContext(), render) + }) } type diffContext struct { From b4f2c0ca3dd03d79d5fabccd347e1316ed4d0dd5 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 16 Jul 2025 13:50:31 +0200 Subject: [PATCH 085/193] cli/command/container: newDiffContext: use struct-literal Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 0db7b9f774d980cb2aba6e9bb2b6e8fd09d6ac77) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/formatter_diff.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cli/command/container/formatter_diff.go b/cli/command/container/formatter_diff.go index 45c45da9b3ad..0d74e4260771 100644 --- a/cli/command/container/formatter_diff.go +++ b/cli/command/container/formatter_diff.go @@ -45,12 +45,14 @@ type diffContext struct { } func newDiffContext() *diffContext { - diffCtx := diffContext{} - diffCtx.Header = formatter.SubHeaderContext{ - "Type": changeTypeHeader, - "Path": pathHeader, + return &diffContext{ + HeaderContext: formatter.HeaderContext{ + Header: formatter.SubHeaderContext{ + "Type": changeTypeHeader, + "Path": pathHeader, + }, + }, } - return &diffCtx } func (d *diffContext) MarshalJSON() ([]byte, error) { From 4480024a8088db7e37934181c4f97ea57534779c Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 16 Jul 2025 13:52:04 +0200 Subject: [PATCH 086/193] cli/command/container: deprecate DiffFormatWrite It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit fdc90caeee3cc6c4d9a9569293b2283c8bc54463) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/diff.go | 2 +- cli/command/container/formatter_diff.go | 7 +++++++ cli/command/container/formatter_diff_test.go | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/cli/command/container/diff.go b/cli/command/container/diff.go index d9b4954c564a..74293639a9e2 100644 --- a/cli/command/container/diff.go +++ b/cli/command/container/diff.go @@ -41,5 +41,5 @@ func runDiff(ctx context.Context, dockerCLI command.Cli, containerID string) err Output: dockerCLI.Out(), Format: newDiffFormat("{{.Type}} {{.Path}}"), } - return DiffFormatWrite(diffCtx, changes) + return diffFormatWrite(diffCtx, changes) } diff --git a/cli/command/container/formatter_diff.go b/cli/command/container/formatter_diff.go index 0d74e4260771..32af3ed64504 100644 --- a/cli/command/container/formatter_diff.go +++ b/cli/command/container/formatter_diff.go @@ -28,7 +28,14 @@ func newDiffFormat(source string) formatter.Format { } // DiffFormatWrite writes formatted diff using the Context +// +// Deprecated: this function was only used internally and will be removed in the next release. func DiffFormatWrite(fmtCtx formatter.Context, changes []container.FilesystemChange) error { + return diffFormatWrite(fmtCtx, changes) +} + +// diffFormatWrite writes formatted diff using the [formatter.Context]. +func diffFormatWrite(fmtCtx formatter.Context, changes []container.FilesystemChange) error { return fmtCtx.Write(newDiffContext(), func(format func(subContext formatter.SubContext) error) error { for _, change := range changes { if err := format(&diffContext{c: change}); err != nil { diff --git a/cli/command/container/formatter_diff_test.go b/cli/command/container/formatter_diff_test.go index 0d9ba20d3345..51ff9df6669b 100644 --- a/cli/command/container/formatter_diff_test.go +++ b/cli/command/container/formatter_diff_test.go @@ -50,7 +50,7 @@ D: /usr/app/old_app.js t.Run(string(tc.context.Format), func(t *testing.T) { out := bytes.NewBufferString("") tc.context.Output = out - err := DiffFormatWrite(tc.context, diffs) + err := diffFormatWrite(tc.context, diffs) if err != nil { assert.Error(t, err, tc.expected) } else { From 73fbe6a0200bdb78f866749e9c0c366e0f92aa49 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 21 Aug 2025 13:46:16 +0200 Subject: [PATCH 087/193] cli/command/network: deprecate NewFormat, FormatWrite It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit e3903a1ac8722714f8413ccdd28f4bb03b2cde0d) Signed-off-by: Sebastiaan van Stijn --- cli/command/network/formatter.go | 22 ++++++++++++++++++---- cli/command/network/formatter_test.go | 22 +++++++++++----------- cli/command/network/list.go | 4 ++-- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/cli/command/network/formatter.go b/cli/command/network/formatter.go index 6a15a6be1070..f002cc62d3b9 100644 --- a/cli/command/network/formatter.go +++ b/cli/command/network/formatter.go @@ -17,8 +17,15 @@ const ( internalHeader = "INTERNAL" ) -// NewFormat returns a Format for rendering using a network Context +// NewFormat returns a Format for rendering using a network Context. +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewFormat(source string, quiet bool) formatter.Format { + return newFormat(source, quiet) +} + +// newFormat returns a [formatter.Format] for rendering a networkContext. +func newFormat(source string, quiet bool) formatter.Format { switch source { case formatter.TableFormatKey: if quiet { @@ -35,10 +42,17 @@ func NewFormat(source string, quiet bool) formatter.Format { } // FormatWrite writes the context -func FormatWrite(ctx formatter.Context, networks []network.Summary) error { +// +// Deprecated: this function was only used internally and will be removed in the next release. +func FormatWrite(fmtCtx formatter.Context, networks []network.Summary) error { + return formatWrite(fmtCtx, networks) +} + +// formatWrite writes the context. +func formatWrite(fmtCtx formatter.Context, networks []network.Summary) error { render := func(format func(subContext formatter.SubContext) error) error { for _, nw := range networks { - networkCtx := &networkContext{trunc: ctx.Trunc, n: nw} + networkCtx := &networkContext{trunc: fmtCtx.Trunc, n: nw} if err := format(networkCtx); err != nil { return err } @@ -57,7 +71,7 @@ func FormatWrite(ctx formatter.Context, networks []network.Summary) error { "Labels": formatter.LabelsHeader, "CreatedAt": formatter.CreatedAtHeader, } - return ctx.Write(&networkCtx, render) + return fmtCtx.Write(&networkCtx, render) } type networkContext struct { diff --git a/cli/command/network/formatter_test.go b/cli/command/network/formatter_test.go index ddeeb895fdb6..f0dae0377877 100644 --- a/cli/command/network/formatter_test.go +++ b/cli/command/network/formatter_test.go @@ -91,27 +91,27 @@ func TestNetworkContextWrite(t *testing.T) { }, // Table format { - formatter.Context{Format: NewFormat("table", false)}, + formatter.Context{Format: newFormat("table", false)}, `NETWORK ID NAME DRIVER SCOPE networkID1 foobar_baz foo local networkID2 foobar_bar bar local `, }, { - formatter.Context{Format: NewFormat("table", true)}, + formatter.Context{Format: newFormat("table", true)}, `networkID1 networkID2 `, }, { - formatter.Context{Format: NewFormat("table {{.Name}}", false)}, + formatter.Context{Format: newFormat("table {{.Name}}", false)}, `NAME foobar_baz foobar_bar `, }, { - formatter.Context{Format: NewFormat("table {{.Name}}", true)}, + formatter.Context{Format: newFormat("table {{.Name}}", true)}, `NAME foobar_baz foobar_bar @@ -119,7 +119,7 @@ foobar_bar }, // Raw Format { - formatter.Context{Format: NewFormat("raw", false)}, + formatter.Context{Format: newFormat("raw", false)}, `network_id: networkID1 name: foobar_baz driver: foo @@ -133,21 +133,21 @@ scope: local `, }, { - formatter.Context{Format: NewFormat("raw", true)}, + formatter.Context{Format: newFormat("raw", true)}, `network_id: networkID1 network_id: networkID2 `, }, // Custom Format { - formatter.Context{Format: NewFormat("{{.Name}}", false)}, + formatter.Context{Format: newFormat("{{.Name}}", false)}, `foobar_baz foobar_bar `, }, // Custom Format with CreatedAt { - formatter.Context{Format: NewFormat("{{.Name}} {{.CreatedAt}}", false)}, + formatter.Context{Format: newFormat("{{.Name}} {{.CreatedAt}}", false)}, `foobar_baz 2016-01-01 00:00:00 +0000 UTC foobar_bar 2017-01-01 00:00:00 +0000 UTC `, @@ -166,7 +166,7 @@ foobar_bar 2017-01-01 00:00:00 +0000 UTC t.Run(string(tc.context.Format), func(t *testing.T) { var out bytes.Buffer tc.context.Output = &out - err := FormatWrite(tc.context, networks) + err := formatWrite(tc.context, networks) if err != nil { assert.Error(t, err, tc.expected) } else { @@ -187,7 +187,7 @@ func TestNetworkContextWriteJSON(t *testing.T) { } out := bytes.NewBufferString("") - err := FormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, networks) + err := formatWrite(formatter.Context{Format: "{{json .}}", Output: out}, networks) if err != nil { t.Fatal(err) } @@ -206,7 +206,7 @@ func TestNetworkContextWriteJSONField(t *testing.T) { {ID: "networkID2", Name: "foobar_bar"}, } out := bytes.NewBufferString("") - err := FormatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, networks) + err := formatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, networks) if err != nil { t.Fatal(err) } diff --git a/cli/command/network/list.go b/cli/command/network/list.go index 0fb3934103a5..aac8df9629b6 100644 --- a/cli/command/network/list.go +++ b/cli/command/network/list.go @@ -67,8 +67,8 @@ func runList(ctx context.Context, dockerCli command.Cli, options listOptions) er networksCtx := formatter.Context{ Output: dockerCli.Out(), - Format: NewFormat(format, options.quiet), + Format: newFormat(format, options.quiet), Trunc: !options.noTrunc, } - return FormatWrite(networksCtx, networkResources) + return formatWrite(networksCtx, networkResources) } From 09a36f2ef1a63591c8aeb00b26773c05a9430595 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 21 Aug 2025 14:01:22 +0200 Subject: [PATCH 088/193] cli/command/image: deprecate NewHistoryFormat, HistoryWrite It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 15cf4fa912cc6319110065d4f8c83cc6517ca8b2) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/formatter_history.go | 20 +++++++++++++++++--- cli/command/image/formatter_history_test.go | 6 +++--- cli/command/image/history.go | 4 ++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/cli/command/image/formatter_history.go b/cli/command/image/formatter_history.go index e2fcd155ce23..2a71fb94a1d2 100644 --- a/cli/command/image/formatter_history.go +++ b/cli/command/image/formatter_history.go @@ -20,7 +20,14 @@ const ( ) // NewHistoryFormat returns a format for rendering an HistoryContext +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewHistoryFormat(source string, quiet bool, human bool) formatter.Format { + return newHistoryFormat(source, quiet, human) +} + +// newHistoryFormat returns a format for rendering a historyContext. +func newHistoryFormat(source string, quiet bool, human bool) formatter.Format { if source == formatter.TableFormatKey { switch { case quiet: @@ -36,10 +43,17 @@ func NewHistoryFormat(source string, quiet bool, human bool) formatter.Format { } // HistoryWrite writes the context -func HistoryWrite(ctx formatter.Context, human bool, histories []image.HistoryResponseItem) error { +// +// Deprecated: this function was only used internally and will be removed in the next release. +func HistoryWrite(fmtCtx formatter.Context, human bool, histories []image.HistoryResponseItem) error { + return historyWrite(fmtCtx, human, histories) +} + +// historyWrite writes the context +func historyWrite(fmtCtx formatter.Context, human bool, histories []image.HistoryResponseItem) error { render := func(format func(subContext formatter.SubContext) error) error { for _, history := range histories { - historyCtx := &historyContext{trunc: ctx.Trunc, h: history, human: human} + historyCtx := &historyContext{trunc: fmtCtx.Trunc, h: history, human: human} if err := format(historyCtx); err != nil { return err } @@ -55,7 +69,7 @@ func HistoryWrite(ctx formatter.Context, human bool, histories []image.HistoryRe "Size": formatter.SizeHeader, "Comment": commentHeader, } - return ctx.Write(historyCtx, render) + return fmtCtx.Write(historyCtx, render) } type historyContext struct { diff --git a/cli/command/image/formatter_history_test.go b/cli/command/image/formatter_history_test.go index 611f7d50674f..b5e71f6be09a 100644 --- a/cli/command/image/formatter_history_test.go +++ b/cli/command/image/formatter_history_test.go @@ -237,7 +237,7 @@ imageID6 17 years ago /bin/bash echo 183MB }{ { formatter.Context{ - Format: NewHistoryFormat("table", false, true), + Format: newHistoryFormat("table", false, true), Trunc: true, Output: out, }, @@ -245,7 +245,7 @@ imageID6 17 years ago /bin/bash echo 183MB }, { formatter.Context{ - Format: NewHistoryFormat("table", false, true), + Format: newHistoryFormat("table", false, true), Trunc: false, Output: out, }, @@ -255,7 +255,7 @@ imageID6 17 years ago /bin/bash echo 183MB for _, tc := range cases { t.Run(string(tc.context.Format), func(t *testing.T) { - err := HistoryWrite(tc.context, true, histories) + err := historyWrite(tc.context, true, histories) assert.NilError(t, err) assert.Equal(t, out.String(), tc.expected) // Clean buffer diff --git a/cli/command/image/history.go b/cli/command/image/history.go index 16396b89796c..24817c1ef496 100644 --- a/cli/command/image/history.go +++ b/cli/command/image/history.go @@ -84,8 +84,8 @@ func runHistory(ctx context.Context, dockerCli command.Cli, opts historyOptions) historyCtx := formatter.Context{ Output: dockerCli.Out(), - Format: NewHistoryFormat(format, opts.quiet, opts.human), + Format: newHistoryFormat(format, opts.quiet, opts.human), Trunc: !opts.noTrunc, } - return HistoryWrite(historyCtx, opts.human, history) + return historyWrite(historyCtx, opts.human, history) } From 694e92aa104fbeca25355eb408c399e415b1bc30 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 21 Aug 2025 14:12:01 +0200 Subject: [PATCH 089/193] cli/command/checkpoint: deprecate NewFormat, FormatWrite It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit d861b78a8ac4d98d3c814b798b84e43ce991d26b) Signed-off-by: Sebastiaan van Stijn --- cli/command/checkpoint/formatter.go | 18 ++++++++++++++++-- cli/command/checkpoint/formatter_test.go | 8 ++++---- cli/command/checkpoint/list.go | 4 ++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/cli/command/checkpoint/formatter.go b/cli/command/checkpoint/formatter.go index 47ee77635b71..4de82043aab3 100644 --- a/cli/command/checkpoint/formatter.go +++ b/cli/command/checkpoint/formatter.go @@ -11,7 +11,14 @@ const ( ) // NewFormat returns a format for use with a checkpoint Context +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewFormat(source string) formatter.Format { + return newFormat(source) +} + +// newFormat returns a format for use with a checkpointContext. +func newFormat(source string) formatter.Format { if source == formatter.TableFormatKey { return defaultCheckpointFormat } @@ -19,7 +26,14 @@ func NewFormat(source string) formatter.Format { } // FormatWrite writes formatted checkpoints using the Context -func FormatWrite(ctx formatter.Context, checkpoints []checkpoint.Summary) error { +// +// Deprecated: this function was only used internally and will be removed in the next release. +func FormatWrite(fmtCtx formatter.Context, checkpoints []checkpoint.Summary) error { + return formatWrite(fmtCtx, checkpoints) +} + +// formatWrite writes formatted checkpoints using the Context +func formatWrite(fmtCtx formatter.Context, checkpoints []checkpoint.Summary) error { render := func(format func(subContext formatter.SubContext) error) error { for _, cp := range checkpoints { if err := format(&checkpointContext{c: cp}); err != nil { @@ -28,7 +42,7 @@ func FormatWrite(ctx formatter.Context, checkpoints []checkpoint.Summary) error } return nil } - return ctx.Write(newCheckpointContext(), render) + return fmtCtx.Write(newCheckpointContext(), render) } type checkpointContext struct { diff --git a/cli/command/checkpoint/formatter_test.go b/cli/command/checkpoint/formatter_test.go index 7f265294bab7..1cca11e24337 100644 --- a/cli/command/checkpoint/formatter_test.go +++ b/cli/command/checkpoint/formatter_test.go @@ -15,7 +15,7 @@ func TestCheckpointContextFormatWrite(t *testing.T) { expected string }{ { - formatter.Context{Format: NewFormat(defaultCheckpointFormat)}, + formatter.Context{Format: newFormat(defaultCheckpointFormat)}, `CHECKPOINT NAME checkpoint-1 checkpoint-2 @@ -23,14 +23,14 @@ checkpoint-3 `, }, { - formatter.Context{Format: NewFormat("{{.Name}}")}, + formatter.Context{Format: newFormat("{{.Name}}")}, `checkpoint-1 checkpoint-2 checkpoint-3 `, }, { - formatter.Context{Format: NewFormat("{{.Name}}:")}, + formatter.Context{Format: newFormat("{{.Name}}:")}, `checkpoint-1: checkpoint-2: checkpoint-3: @@ -41,7 +41,7 @@ checkpoint-3: for _, testcase := range cases { out := bytes.NewBufferString("") testcase.context.Output = out - err := FormatWrite(testcase.context, []checkpoint.Summary{ + err := formatWrite(testcase.context, []checkpoint.Summary{ {Name: "checkpoint-1"}, {Name: "checkpoint-2"}, {Name: "checkpoint-3"}, diff --git a/cli/command/checkpoint/list.go b/cli/command/checkpoint/list.go index 55344e08c9d5..0fe769686857 100644 --- a/cli/command/checkpoint/list.go +++ b/cli/command/checkpoint/list.go @@ -45,7 +45,7 @@ func runList(ctx context.Context, dockerCli command.Cli, container string, opts cpCtx := formatter.Context{ Output: dockerCli.Out(), - Format: NewFormat(formatter.TableFormatKey), + Format: newFormat(formatter.TableFormatKey), } - return FormatWrite(cpCtx, checkpoints) + return formatWrite(cpCtx, checkpoints) } From 399861e7d28043fd7b57fba537d7646e7ab8d93c Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 21 Aug 2025 14:21:56 +0200 Subject: [PATCH 090/193] cli/command/config: deprecate NewFormat, FormatWrite, InspectFormatWrite It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit e626f778ec6e2f5b38c5a87f307e94331c5acf0c) Signed-off-by: Sebastiaan van Stijn --- cli/command/config/formatter.go | 33 +++++++++++++++++++++++----- cli/command/config/formatter_test.go | 8 +++---- cli/command/config/inspect.go | 4 ++-- cli/command/config/ls.go | 4 ++-- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/cli/command/config/formatter.go b/cli/command/config/formatter.go index f2defa721b80..32521c749229 100644 --- a/cli/command/config/formatter.go +++ b/cli/command/config/formatter.go @@ -30,7 +30,14 @@ Data: ) // NewFormat returns a Format for rendering using a config Context +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewFormat(source string, quiet bool) formatter.Format { + return newFormat(source, quiet) +} + +// newFormat returns a Format for rendering using a configContext. +func newFormat(source string, quiet bool) formatter.Format { switch source { case formatter.PrettyFormatKey: return configInspectPrettyTemplate @@ -44,7 +51,14 @@ func NewFormat(source string, quiet bool) formatter.Format { } // FormatWrite writes the context -func FormatWrite(ctx formatter.Context, configs []swarm.Config) error { +// +// Deprecated: this function was only used internally and will be removed in the next release. +func FormatWrite(fmtCtx formatter.Context, configs []swarm.Config) error { + return formatWrite(fmtCtx, configs) +} + +// formatWrite writes the context +func formatWrite(fmtCtx formatter.Context, configs []swarm.Config) error { render := func(format func(subContext formatter.SubContext) error) error { for _, config := range configs { configCtx := &configContext{c: config} @@ -54,7 +68,7 @@ func FormatWrite(ctx formatter.Context, configs []swarm.Config) error { } return nil } - return ctx.Write(newConfigContext(), render) + return fmtCtx.Write(newConfigContext(), render) } func newConfigContext() *configContext { @@ -115,9 +129,16 @@ func (c *configContext) Label(name string) string { } // InspectFormatWrite renders the context for a list of configs -func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.GetRefFunc) error { - if ctx.Format != configInspectPrettyTemplate { - return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef) +// +// Deprecated: this function was only used internally and will be removed in the next release. +func InspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef inspect.GetRefFunc) error { + return inspectFormatWrite(fmtCtx, refs, getRef) +} + +// inspectFormatWrite renders the context for a list of configs +func inspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef inspect.GetRefFunc) error { + if fmtCtx.Format != configInspectPrettyTemplate { + return inspect.Inspect(fmtCtx.Output, refs, string(fmtCtx.Format), getRef) } render := func(format func(subContext formatter.SubContext) error) error { for _, ref := range refs { @@ -135,7 +156,7 @@ func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.Get } return nil } - return ctx.Write(&configInspectContext{}, render) + return fmtCtx.Write(&configInspectContext{}, render) } type configInspectContext struct { diff --git a/cli/command/config/formatter_test.go b/cli/command/config/formatter_test.go index 8736a876a48b..7ddead7d56e8 100644 --- a/cli/command/config/formatter_test.go +++ b/cli/command/config/formatter_test.go @@ -27,21 +27,21 @@ func TestConfigContextFormatWrite(t *testing.T) { }, // Table format { - formatter.Context{Format: NewFormat("table", false)}, + formatter.Context{Format: newFormat("table", false)}, `ID NAME CREATED UPDATED 1 passwords Less than a second ago Less than a second ago 2 id_rsa Less than a second ago Less than a second ago `, }, { - formatter.Context{Format: NewFormat("table {{.Name}}", true)}, + formatter.Context{Format: newFormat("table {{.Name}}", true)}, `NAME passwords id_rsa `, }, { - formatter.Context{Format: NewFormat("{{.ID}}-{{.Name}}", false)}, + formatter.Context{Format: newFormat("{{.ID}}-{{.Name}}", false)}, `1-passwords 2-id_rsa `, @@ -64,7 +64,7 @@ id_rsa t.Run(string(tc.context.Format), func(t *testing.T) { var out bytes.Buffer tc.context.Output = &out - if err := FormatWrite(tc.context, configs); err != nil { + if err := formatWrite(tc.context, configs); err != nil { assert.ErrorContains(t, err, tc.expected) } else { assert.Equal(t, out.String(), tc.expected) diff --git a/cli/command/config/inspect.go b/cli/command/config/inspect.go index 1983857eb578..d25af89a794b 100644 --- a/cli/command/config/inspect.go +++ b/cli/command/config/inspect.go @@ -63,10 +63,10 @@ func RunConfigInspect(ctx context.Context, dockerCLI command.Cli, opts InspectOp configCtx := formatter.Context{ Output: dockerCLI.Out(), - Format: NewFormat(f, false), + Format: newFormat(f, false), } - if err := InspectFormatWrite(configCtx, opts.Names, getRef); err != nil { + if err := inspectFormatWrite(configCtx, opts.Names, getRef); err != nil { return cli.StatusError{StatusCode: 1, Status: err.Error()} } return nil diff --git a/cli/command/config/ls.go b/cli/command/config/ls.go index f6e68cfaa2ef..c3e629c9973e 100644 --- a/cli/command/config/ls.go +++ b/cli/command/config/ls.go @@ -68,7 +68,7 @@ func RunConfigList(ctx context.Context, dockerCLI command.Cli, options ListOptio configCtx := formatter.Context{ Output: dockerCLI.Out(), - Format: NewFormat(format, options.Quiet), + Format: newFormat(format, options.Quiet), } - return FormatWrite(configCtx, configs) + return formatWrite(configCtx, configs) } From e424c503737298c64236ff86cc7c074aeb38266e Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 21 Aug 2025 14:36:20 +0200 Subject: [PATCH 091/193] cli/command/node: deprecate NewFormat, FormatWrite, InspectFormatWrite It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 123ef81f7db4ec9e99083a8dc6aae00f3c9ecfcd) Signed-off-by: Sebastiaan van Stijn --- cli/command/node/formatter.go | 33 ++++++++++++++++++++++++------ cli/command/node/formatter_test.go | 28 ++++++++++++------------- cli/command/node/inspect.go | 4 ++-- cli/command/node/list.go | 4 ++-- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/cli/command/node/formatter.go b/cli/command/node/formatter.go index c49ad30abe5b..36ad47d6563b 100644 --- a/cli/command/node/formatter.go +++ b/cli/command/node/formatter.go @@ -80,7 +80,14 @@ TLS Info: ) // NewFormat returns a Format for rendering using a node Context +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewFormat(source string, quiet bool) formatter.Format { + return newFormat(source, quiet) +} + +// newFormat returns a Format for rendering using a nodeContext. +func newFormat(source string, quiet bool) formatter.Format { switch source { case formatter.PrettyFormatKey: return nodeInspectPrettyTemplate @@ -99,7 +106,14 @@ func NewFormat(source string, quiet bool) formatter.Format { } // FormatWrite writes the context -func FormatWrite(ctx formatter.Context, nodes []swarm.Node, info system.Info) error { +// +// Deprecated: this function was only used internally and will be removed in the next release. +func FormatWrite(fmtCtx formatter.Context, nodes []swarm.Node, info system.Info) error { + return formatWrite(fmtCtx, nodes, info) +} + +// formatWrite writes the context. +func formatWrite(fmtCtx formatter.Context, nodes []swarm.Node, info system.Info) error { render := func(format func(subContext formatter.SubContext) error) error { for _, node := range nodes { nodeCtx := &nodeContext{n: node, info: info} @@ -120,7 +134,7 @@ func FormatWrite(ctx formatter.Context, nodes []swarm.Node, info system.Info) er "EngineVersion": engineVersionHeader, "TLSStatus": tlsStatusHeader, } - return ctx.Write(&nodeCtx, render) + return fmtCtx.Write(&nodeCtx, render) } type nodeContext struct { @@ -180,9 +194,16 @@ func (c *nodeContext) EngineVersion() string { } // InspectFormatWrite renders the context for a list of nodes -func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.GetRefFunc) error { - if ctx.Format != nodeInspectPrettyTemplate { - return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef) +// +// Deprecated: this function was only used internally and will be removed in the next release. +func InspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef inspect.GetRefFunc) error { + return inspectFormatWrite(fmtCtx, refs, getRef) +} + +// inspectFormatWrite renders the context for a list of nodes. +func inspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef inspect.GetRefFunc) error { + if fmtCtx.Format != nodeInspectPrettyTemplate { + return inspect.Inspect(fmtCtx.Output, refs, string(fmtCtx.Format), getRef) } render := func(format func(subContext formatter.SubContext) error) error { for _, ref := range refs { @@ -200,7 +221,7 @@ func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.Get } return nil } - return ctx.Write(&nodeInspectContext{}, render) + return fmtCtx.Write(&nodeInspectContext{}, render) } type nodeInspectContext struct { diff --git a/cli/command/node/formatter_test.go b/cli/command/node/formatter_test.go index 8983f337a6cb..8dcdbdced9d0 100644 --- a/cli/command/node/formatter_test.go +++ b/cli/command/node/formatter_test.go @@ -74,7 +74,7 @@ func TestNodeContextWrite(t *testing.T) { }, // Table format { - context: formatter.Context{Format: NewFormat("table", false)}, + context: formatter.Context{Format: newFormat("table", false)}, expected: `ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION nodeID1 foobar_baz Foo Drain Leader 18.03.0-ce nodeID2 foobar_bar Bar Active Reachable 1.2.3 @@ -82,7 +82,7 @@ nodeID3 foobar_boo Boo Active ` + "\n", // clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { - context: formatter.Context{Format: NewFormat("table", true)}, + context: formatter.Context{Format: newFormat("table", true)}, expected: `nodeID1 nodeID2 nodeID3 @@ -90,7 +90,7 @@ nodeID3 clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { - context: formatter.Context{Format: NewFormat("table {{.Hostname}}", false)}, + context: formatter.Context{Format: newFormat("table {{.Hostname}}", false)}, expected: `HOSTNAME foobar_baz foobar_bar @@ -99,7 +99,7 @@ foobar_boo clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { - context: formatter.Context{Format: NewFormat("table {{.Hostname}}", true)}, + context: formatter.Context{Format: newFormat("table {{.Hostname}}", true)}, expected: `HOSTNAME foobar_baz foobar_bar @@ -108,7 +108,7 @@ foobar_boo clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { - context: formatter.Context{Format: NewFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)}, + context: formatter.Context{Format: newFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)}, expected: `ID HOSTNAME TLS STATUS nodeID1 foobar_baz Needs Rotation nodeID2 foobar_bar Ready @@ -117,7 +117,7 @@ nodeID3 foobar_boo Unknown clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { // no cluster TLS status info, TLS status for all nodes is unknown - context: formatter.Context{Format: NewFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)}, + context: formatter.Context{Format: newFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)}, expected: `ID HOSTNAME TLS STATUS nodeID1 foobar_baz Unknown nodeID2 foobar_bar Unknown @@ -127,7 +127,7 @@ nodeID3 foobar_boo Unknown }, // Raw Format { - context: formatter.Context{Format: NewFormat("raw", false)}, + context: formatter.Context{Format: newFormat("raw", false)}, expected: `node_id: nodeID1 hostname: foobar_baz status: Foo @@ -148,7 +148,7 @@ manager_status: ` + "\n\n", // to preserve whitespace clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { - context: formatter.Context{Format: NewFormat("raw", true)}, + context: formatter.Context{Format: newFormat("raw", true)}, expected: `node_id: nodeID1 node_id: nodeID2 node_id: nodeID3 @@ -157,7 +157,7 @@ node_id: nodeID3 }, // Custom Format { - context: formatter.Context{Format: NewFormat("{{.Hostname}} {{.TLSStatus}}", false)}, + context: formatter.Context{Format: newFormat("{{.Hostname}} {{.TLSStatus}}", false)}, expected: `foobar_baz Needs Rotation foobar_bar Ready foobar_boo Unknown @@ -205,7 +205,7 @@ foobar_boo Unknown var out bytes.Buffer tc.context.Output = &out - err := FormatWrite(tc.context, nodes, system.Info{Swarm: swarm.Info{Cluster: &tc.clusterInfo}}) + err := formatWrite(tc.context, nodes, system.Info{Swarm: swarm.Info{Cluster: &tc.clusterInfo}}) if err != nil { assert.Error(t, err, tc.expected) } else { @@ -252,7 +252,7 @@ func TestNodeContextWriteJSON(t *testing.T) { {ID: "nodeID3", Description: swarm.NodeDescription{Hostname: "foobar_boo", Engine: swarm.EngineDescription{EngineVersion: "18.03.0-ce"}}}, } out := bytes.NewBufferString("") - err := FormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, nodes, testcase.info) + err := formatWrite(formatter.Context{Format: "{{json .}}", Output: out}, nodes, testcase.info) if err != nil { t.Fatal(err) } @@ -272,7 +272,7 @@ func TestNodeContextWriteJSONField(t *testing.T) { {ID: "nodeID2", Description: swarm.NodeDescription{Hostname: "foobar_bar"}}, } out := bytes.NewBufferString("") - err := FormatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, nodes, system.Info{}) + err := formatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, nodes, system.Info{}) if err != nil { t.Fatal(err) } @@ -317,10 +317,10 @@ func TestNodeInspectWriteContext(t *testing.T) { } out := bytes.NewBufferString("") context := formatter.Context{ - Format: NewFormat("pretty", false), + Format: newFormat("pretty", false), Output: out, } - err := InspectFormatWrite(context, []string{"nodeID1"}, func(string) (any, []byte, error) { + err := inspectFormatWrite(context, []string{"nodeID1"}, func(string) (any, []byte, error) { return node, nil, nil }) if err != nil { diff --git a/cli/command/node/inspect.go b/cli/command/node/inspect.go index f293861d478b..8f2519e04e34 100644 --- a/cli/command/node/inspect.go +++ b/cli/command/node/inspect.go @@ -66,10 +66,10 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) nodeCtx := formatter.Context{ Output: dockerCli.Out(), - Format: NewFormat(f, false), + Format: newFormat(f, false), } - if err := InspectFormatWrite(nodeCtx, opts.nodeIds, getRef); err != nil { + if err := inspectFormatWrite(nodeCtx, opts.nodeIds, getRef); err != nil { return cli.StatusError{StatusCode: 1, Status: err.Error()} } return nil diff --git a/cli/command/node/list.go b/cli/command/node/list.go index 3d319deeb946..2f9e6cad1f95 100644 --- a/cli/command/node/list.go +++ b/cli/command/node/list.go @@ -79,10 +79,10 @@ func runList(ctx context.Context, dockerCli command.Cli, options listOptions) er nodesCtx := formatter.Context{ Output: dockerCli.Out(), - Format: NewFormat(format, options.quiet), + Format: newFormat(format, options.quiet), } sort.Slice(nodes, func(i, j int) bool { return sortorder.NaturalLess(nodes[i].Description.Hostname, nodes[j].Description.Hostname) }) - return FormatWrite(nodesCtx, nodes, info) + return formatWrite(nodesCtx, nodes, info) } From 22c68c536651b24633f14daa387eda3a0a7c17ad Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 21 Aug 2025 14:41:18 +0200 Subject: [PATCH 092/193] cli/command/plugin: deprecate NewFormat, FormatWrite It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit bf47419852f5a41bc079b8f377091780f8bdfe63) Signed-off-by: Sebastiaan van Stijn --- cli/command/plugin/formatter.go | 22 ++++++++++++++++++---- cli/command/plugin/formatter_test.go | 20 ++++++++++---------- cli/command/plugin/list.go | 4 ++-- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/cli/command/plugin/formatter.go b/cli/command/plugin/formatter.go index d468c07e0a09..07ecf7d40956 100644 --- a/cli/command/plugin/formatter.go +++ b/cli/command/plugin/formatter.go @@ -21,7 +21,14 @@ enabled: {{.Enabled}} ) // NewFormat returns a Format for rendering using a plugin Context +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewFormat(source string, quiet bool) formatter.Format { + return newFormat(source, quiet) +} + +// newFormat returns a Format for rendering using a pluginContext. +func newFormat(source string, quiet bool) formatter.Format { switch source { case formatter.TableFormatKey: if quiet { @@ -38,10 +45,17 @@ func NewFormat(source string, quiet bool) formatter.Format { } // FormatWrite writes the context -func FormatWrite(ctx formatter.Context, plugins []*types.Plugin) error { +// +// Deprecated: this function was only used internally and will be removed in the next release. +func FormatWrite(fmtCtx formatter.Context, plugins []*types.Plugin) error { + return formatWrite(fmtCtx, plugins) +} + +// formatWrite writes the context +func formatWrite(fmtCtx formatter.Context, plugins []*types.Plugin) error { render := func(format func(subContext formatter.SubContext) error) error { - for _, plugin := range plugins { - pluginCtx := &pluginContext{trunc: ctx.Trunc, p: *plugin} + for _, p := range plugins { + pluginCtx := &pluginContext{trunc: fmtCtx.Trunc, p: *p} if err := format(pluginCtx); err != nil { return err } @@ -56,7 +70,7 @@ func FormatWrite(ctx formatter.Context, plugins []*types.Plugin) error { "Enabled": enabledHeader, "PluginReference": formatter.ImageHeader, } - return ctx.Write(&pluginCtx, render) + return fmtCtx.Write(&pluginCtx, render) } type pluginContext struct { diff --git a/cli/command/plugin/formatter_test.go b/cli/command/plugin/formatter_test.go index 513cde4c4828..ddaa68c859ec 100644 --- a/cli/command/plugin/formatter_test.go +++ b/cli/command/plugin/formatter_test.go @@ -86,7 +86,7 @@ func TestPluginContextWrite(t *testing.T) { }, { doc: "table format", - context: formatter.Context{Format: NewFormat("table", false)}, + context: formatter.Context{Format: newFormat("table", false)}, expected: `ID NAME DESCRIPTION ENABLED pluginID1 foobar_baz description 1 true pluginID2 foobar_bar description 2 false @@ -94,14 +94,14 @@ pluginID2 foobar_bar description 2 false }, { doc: "table format, quiet", - context: formatter.Context{Format: NewFormat("table", true)}, + context: formatter.Context{Format: newFormat("table", true)}, expected: `pluginID1 pluginID2 `, }, { doc: "table format name col", - context: formatter.Context{Format: NewFormat("table {{.Name}}", false)}, + context: formatter.Context{Format: newFormat("table {{.Name}}", false)}, expected: `NAME foobar_baz foobar_bar @@ -109,7 +109,7 @@ foobar_bar }, { doc: "table format name col, quiet", - context: formatter.Context{Format: NewFormat("table {{.Name}}", true)}, + context: formatter.Context{Format: newFormat("table {{.Name}}", true)}, expected: `NAME foobar_baz foobar_bar @@ -117,7 +117,7 @@ foobar_bar }, { doc: "raw format", - context: formatter.Context{Format: NewFormat("raw", false)}, + context: formatter.Context{Format: newFormat("raw", false)}, expected: `plugin_id: pluginID1 name: foobar_baz description: description 1 @@ -132,14 +132,14 @@ enabled: false }, { doc: "raw format, quiet", - context: formatter.Context{Format: NewFormat("raw", true)}, + context: formatter.Context{Format: newFormat("raw", true)}, expected: `plugin_id: pluginID1 plugin_id: pluginID2 `, }, { doc: "custom format", - context: formatter.Context{Format: NewFormat("{{.Name}}", false)}, + context: formatter.Context{Format: newFormat("{{.Name}}", false)}, expected: `foobar_baz foobar_bar `, @@ -156,7 +156,7 @@ foobar_bar var out bytes.Buffer tc.context.Output = &out - err := FormatWrite(tc.context, plugins) + err := formatWrite(tc.context, plugins) if err != nil { assert.Error(t, err, tc.expected) } else { @@ -177,7 +177,7 @@ func TestPluginContextWriteJSON(t *testing.T) { } out := bytes.NewBufferString("") - err := FormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, plugins) + err := formatWrite(formatter.Context{Format: "{{json .}}", Output: out}, plugins) if err != nil { t.Fatal(err) } @@ -196,7 +196,7 @@ func TestPluginContextWriteJSONField(t *testing.T) { {ID: "pluginID2", Name: "foobar_bar"}, } out := bytes.NewBufferString("") - err := FormatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, plugins) + err := formatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, plugins) if err != nil { t.Fatal(err) } diff --git a/cli/command/plugin/list.go b/cli/command/plugin/list.go index 0c7234a2c3b0..da08a06f955c 100644 --- a/cli/command/plugin/list.go +++ b/cli/command/plugin/list.go @@ -66,8 +66,8 @@ func runList(ctx context.Context, dockerCli command.Cli, options listOptions) er pluginsCtx := formatter.Context{ Output: dockerCli.Out(), - Format: NewFormat(format, options.quiet), + Format: newFormat(format, options.quiet), Trunc: !options.noTrunc, } - return FormatWrite(pluginsCtx, plugins) + return formatWrite(pluginsCtx, plugins) } From e8cf8e65f7737f62d190a6934ebaeb5c6a708dc1 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 21 Aug 2025 14:46:59 +0200 Subject: [PATCH 093/193] cli/command/registry: deprecate NewSearchFormat, SearchWrite It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 83371c2014711110aaf375c146ca0912a4a5ba85) Signed-off-by: Sebastiaan van Stijn --- cli/command/registry/formatter_search.go | 22 +++++++++++++++---- cli/command/registry/formatter_search_test.go | 10 ++++----- cli/command/registry/search.go | 4 ++-- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/cli/command/registry/formatter_search.go b/cli/command/registry/formatter_search.go index 23f56ac69e63..2de91434a29f 100644 --- a/cli/command/registry/formatter_search.go +++ b/cli/command/registry/formatter_search.go @@ -16,8 +16,15 @@ const ( automatedHeader = "AUTOMATED" ) -// NewSearchFormat returns a Format for rendering using a network Context +// NewSearchFormat returns a Format for rendering using a search Context +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewSearchFormat(source string) formatter.Format { + return newFormat(source) +} + +// newFormat returns a Format for rendering using a searchContext. +func newFormat(source string) formatter.Format { switch source { case "", formatter.TableFormatKey: return defaultSearchTableFormat @@ -26,10 +33,17 @@ func NewSearchFormat(source string) formatter.Format { } // SearchWrite writes the context -func SearchWrite(ctx formatter.Context, results []registrytypes.SearchResult) error { +// +// Deprecated: this function was only used internally and will be removed in the next release. +func SearchWrite(fmtCtx formatter.Context, results []registrytypes.SearchResult) error { + return formatWrite(fmtCtx, results) +} + +// formatWrite writes the context. +func formatWrite(fmtCtx formatter.Context, results []registrytypes.SearchResult) error { render := func(format func(subContext formatter.SubContext) error) error { for _, result := range results { - searchCtx := &searchContext{trunc: ctx.Trunc, s: result} + searchCtx := &searchContext{trunc: fmtCtx.Trunc, s: result} if err := format(searchCtx); err != nil { return err } @@ -43,7 +57,7 @@ func SearchWrite(ctx formatter.Context, results []registrytypes.SearchResult) er "StarCount": starsHeader, "IsOfficial": officialHeader, } - return ctx.Write(&searchCtx, render) + return fmtCtx.Write(&searchCtx, render) } type searchContext struct { diff --git a/cli/command/registry/formatter_search_test.go b/cli/command/registry/formatter_search_test.go index 082b0377e030..e8cb6456f32c 100644 --- a/cli/command/registry/formatter_search_test.go +++ b/cli/command/registry/formatter_search_test.go @@ -157,12 +157,12 @@ func TestSearchContextWrite(t *testing.T) { }, { doc: "Table format", - format: NewSearchFormat("table"), + format: newFormat("table"), expected: string(golden.Get(t, "search-context-write-table.golden")), }, { doc: "Table format, single column", - format: NewSearchFormat("table {{.Name}}"), + format: newFormat("table {{.Name}}"), expected: `NAME result1 result2 @@ -170,14 +170,14 @@ result2 }, { doc: "Custom format, single field", - format: NewSearchFormat("{{.Name}}"), + format: newFormat("{{.Name}}"), expected: `result1 result2 `, }, { doc: "Custom Format, two columns", - format: NewSearchFormat("{{.Name}} {{.StarCount}}"), + format: newFormat("{{.Name}} {{.StarCount}}"), expected: `result1 5000 result2 5 `, @@ -192,7 +192,7 @@ result2 5 for _, tc := range cases { t.Run(tc.doc, func(t *testing.T) { var out bytes.Buffer - err := SearchWrite(formatter.Context{Format: tc.format, Output: &out}, results) + err := formatWrite(formatter.Context{Format: tc.format, Output: &out}, results) if tc.expectedErr != "" { assert.Check(t, is.Error(err, tc.expectedErr)) } else { diff --git a/cli/command/registry/search.go b/cli/command/registry/search.go index b5e1dcf22082..654fe1440f04 100644 --- a/cli/command/registry/search.go +++ b/cli/command/registry/search.go @@ -76,10 +76,10 @@ func runSearch(ctx context.Context, dockerCli command.Cli, options searchOptions searchCtx := formatter.Context{ Output: dockerCli.Out(), - Format: NewSearchFormat(options.format), + Format: newFormat(options.format), Trunc: !options.noTrunc, } - return SearchWrite(searchCtx, results) + return formatWrite(searchCtx, results) } // authConfigKey is the key used to store credentials for Docker Hub. It is From 531ea22e7d6d08b037482177f8a5db4ef9e2cf05 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 21 Aug 2025 14:52:00 +0200 Subject: [PATCH 094/193] cli/command/secret: deprecate NewFormat, FormatWrite, InspectFormatWrite It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit f3088e37a071ef01fb6cb813e7ca0fce75c516ef) Signed-off-by: Sebastiaan van Stijn --- cli/command/secret/formatter.go | 33 +++++++++++++++++++++++----- cli/command/secret/formatter_test.go | 8 +++---- cli/command/secret/inspect.go | 4 ++-- cli/command/secret/ls.go | 4 ++-- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/cli/command/secret/formatter.go b/cli/command/secret/formatter.go index e30547628665..7a7df1384e9e 100644 --- a/cli/command/secret/formatter.go +++ b/cli/command/secret/formatter.go @@ -29,7 +29,14 @@ Updated at: {{.UpdatedAt}}` ) // NewFormat returns a Format for rendering using a secret Context +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewFormat(source string, quiet bool) formatter.Format { + return newFormat(source, quiet) +} + +// newFormat returns a Format for rendering using a secretContext. +func newFormat(source string, quiet bool) formatter.Format { switch source { case formatter.PrettyFormatKey: return secretInspectPrettyTemplate @@ -43,7 +50,14 @@ func NewFormat(source string, quiet bool) formatter.Format { } // FormatWrite writes the context -func FormatWrite(ctx formatter.Context, secrets []swarm.Secret) error { +// +// Deprecated: this function was only used internally and will be removed in the next release. +func FormatWrite(fmtCtx formatter.Context, secrets []swarm.Secret) error { + return formatWrite(fmtCtx, secrets) +} + +// formatWrite writes the context +func formatWrite(fmtCtx formatter.Context, secrets []swarm.Secret) error { render := func(format func(subContext formatter.SubContext) error) error { for _, secret := range secrets { secretCtx := &secretContext{s: secret} @@ -53,7 +67,7 @@ func FormatWrite(ctx formatter.Context, secrets []swarm.Secret) error { } return nil } - return ctx.Write(newSecretContext(), render) + return fmtCtx.Write(newSecretContext(), render) } func newSecretContext() *secretContext { @@ -122,9 +136,16 @@ func (c *secretContext) Label(name string) string { } // InspectFormatWrite renders the context for a list of secrets -func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.GetRefFunc) error { - if ctx.Format != secretInspectPrettyTemplate { - return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef) +// +// Deprecated: this function was only used internally and will be removed in the next release. +func InspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef inspect.GetRefFunc) error { + return inspectFormatWrite(fmtCtx, refs, getRef) +} + +// inspectFormatWrite renders the context for a list of secrets. +func inspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef inspect.GetRefFunc) error { + if fmtCtx.Format != secretInspectPrettyTemplate { + return inspect.Inspect(fmtCtx.Output, refs, string(fmtCtx.Format), getRef) } render := func(format func(subContext formatter.SubContext) error) error { for _, ref := range refs { @@ -142,7 +163,7 @@ func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.Get } return nil } - return ctx.Write(&secretInspectContext{}, render) + return fmtCtx.Write(&secretInspectContext{}, render) } type secretInspectContext struct { diff --git a/cli/command/secret/formatter_test.go b/cli/command/secret/formatter_test.go index 8ede765a57be..0f682454394c 100644 --- a/cli/command/secret/formatter_test.go +++ b/cli/command/secret/formatter_test.go @@ -27,21 +27,21 @@ func TestSecretContextFormatWrite(t *testing.T) { }, // Table format { - formatter.Context{Format: NewFormat("table", false)}, + formatter.Context{Format: newFormat("table", false)}, `ID NAME DRIVER CREATED UPDATED 1 passwords Less than a second ago Less than a second ago 2 id_rsa Less than a second ago Less than a second ago `, }, { - formatter.Context{Format: NewFormat("table {{.Name}}", true)}, + formatter.Context{Format: newFormat("table {{.Name}}", true)}, `NAME passwords id_rsa `, }, { - formatter.Context{Format: NewFormat("{{.ID}}-{{.Name}}", false)}, + formatter.Context{Format: newFormat("{{.ID}}-{{.Name}}", false)}, `1-passwords 2-id_rsa `, @@ -65,7 +65,7 @@ id_rsa var out bytes.Buffer tc.context.Output = &out - if err := FormatWrite(tc.context, secrets); err != nil { + if err := formatWrite(tc.context, secrets); err != nil { assert.Error(t, err, tc.expected) } else { assert.Equal(t, out.String(), tc.expected) diff --git a/cli/command/secret/inspect.go b/cli/command/secret/inspect.go index 22fed4b6e7a1..7712e4be1399 100644 --- a/cli/command/secret/inspect.go +++ b/cli/command/secret/inspect.go @@ -61,10 +61,10 @@ func runSecretInspect(ctx context.Context, dockerCli command.Cli, opts inspectOp secretCtx := formatter.Context{ Output: dockerCli.Out(), - Format: NewFormat(f, false), + Format: newFormat(f, false), } - if err := InspectFormatWrite(secretCtx, opts.names, getRef); err != nil { + if err := inspectFormatWrite(secretCtx, opts.names, getRef); err != nil { return cli.StatusError{StatusCode: 1, Status: err.Error()} } return nil diff --git a/cli/command/secret/ls.go b/cli/command/secret/ls.go index 65d8f23e0e8c..e739d18e59aa 100644 --- a/cli/command/secret/ls.go +++ b/cli/command/secret/ls.go @@ -66,7 +66,7 @@ func runSecretList(ctx context.Context, dockerCli command.Cli, options listOptio secretCtx := formatter.Context{ Output: dockerCli.Out(), - Format: NewFormat(format, options.quiet), + Format: newFormat(format, options.quiet), } - return FormatWrite(secretCtx, secrets) + return formatWrite(secretCtx, secrets) } From b0b7dcfe86045e3686761d84040e725e89fe7ab4 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 21 Aug 2025 14:58:51 +0200 Subject: [PATCH 095/193] cli/command/service: deprecate NewFormat, InspectFormatWrite It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 9f453d3fea42f46c779d4ed0df800e118724177c) Signed-off-by: Sebastiaan van Stijn --- cli/command/service/formatter.go | 22 ++++++++++++++++++---- cli/command/service/inspect.go | 4 ++-- cli/command/service/inspect_test.go | 12 ++++++------ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/cli/command/service/formatter.go b/cli/command/service/formatter.go index aee5453f10f9..ed22e48331c6 100644 --- a/cli/command/service/formatter.go +++ b/cli/command/service/formatter.go @@ -196,7 +196,14 @@ Ports: ` // NewFormat returns a Format for rendering using a Context +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewFormat(source string) formatter.Format { + return newFormat(source) +} + +// newFormat returns a Format for rendering using a Context. +func newFormat(source string) formatter.Format { switch source { case formatter.PrettyFormatKey: return serviceInspectPrettyTemplate @@ -218,9 +225,16 @@ func resolveNetworks(service swarm.Service, getNetwork inspect.GetRefFunc) map[s } // InspectFormatWrite renders the context for a list of services -func InspectFormatWrite(ctx formatter.Context, refs []string, getRef, getNetwork inspect.GetRefFunc) error { - if ctx.Format != serviceInspectPrettyTemplate { - return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef) +// +// Deprecated: this function was only used internally and will be removed in the next release. +func InspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef, getNetwork inspect.GetRefFunc) error { + return inspectFormatWrite(fmtCtx, refs, getRef, getNetwork) +} + +// inspectFormatWrite renders the context for a list of services +func inspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef, getNetwork inspect.GetRefFunc) error { + if fmtCtx.Format != serviceInspectPrettyTemplate { + return inspect.Inspect(fmtCtx.Output, refs, string(fmtCtx.Format), getRef) } render := func(format func(subContext formatter.SubContext) error) error { for _, ref := range refs { @@ -238,7 +252,7 @@ func InspectFormatWrite(ctx formatter.Context, refs []string, getRef, getNetwork } return nil } - return ctx.Write(&serviceInspectContext{}, render) + return fmtCtx.Write(&serviceInspectContext{}, render) } type serviceInspectContext struct { diff --git a/cli/command/service/inspect.go b/cli/command/service/inspect.go index 01edff6ac2e5..2e28507a440e 100644 --- a/cli/command/service/inspect.go +++ b/cli/command/service/inspect.go @@ -97,10 +97,10 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) serviceCtx := formatter.Context{ Output: dockerCli.Out(), - Format: NewFormat(f), + Format: newFormat(f), } - if err := InspectFormatWrite(serviceCtx, opts.refs, getRef, getNetwork); err != nil { + if err := inspectFormatWrite(serviceCtx, opts.refs, getRef, getNetwork); err != nil { return cli.StatusError{StatusCode: 1, Status: err.Error()} } return nil diff --git a/cli/command/service/inspect_test.go b/cli/command/service/inspect_test.go index d7aa42e8f9bd..17e084bd4fba 100644 --- a/cli/command/service/inspect_test.go +++ b/cli/command/service/inspect_test.go @@ -131,7 +131,7 @@ func formatServiceInspect(t *testing.T, format formatter.Format, now time.Time) Format: format, } - err := InspectFormatWrite(ctx, []string{"de179gar9d0o7ltdybungplod"}, + err := inspectFormatWrite(ctx, []string{"de179gar9d0o7ltdybungplod"}, func(ref string) (any, []byte, error) { return s, nil, nil }, @@ -149,12 +149,12 @@ func formatServiceInspect(t *testing.T, format formatter.Format, now time.Time) } func TestPrettyPrint(t *testing.T) { - s := formatServiceInspect(t, NewFormat("pretty"), time.Now()) + s := formatServiceInspect(t, newFormat("pretty"), time.Now()) golden.Assert(t, s, "service-inspect-pretty.golden") } func TestPrettyPrintWithNoUpdateConfig(t *testing.T) { - s := formatServiceInspect(t, NewFormat("pretty"), time.Now()) + s := formatServiceInspect(t, newFormat("pretty"), time.Now()) if strings.Contains(s, "UpdateStatus") { t.Fatal("Pretty print failed before parsing UpdateStatus") } @@ -167,8 +167,8 @@ func TestJSONFormatWithNoUpdateConfig(t *testing.T) { now := time.Now() // s1: [{"ID":..}] // s2: {"ID":..} - s1 := formatServiceInspect(t, NewFormat(""), now) - s2 := formatServiceInspect(t, NewFormat("{{json .}}"), now) + s1 := formatServiceInspect(t, newFormat(""), now) + s2 := formatServiceInspect(t, newFormat("{{json .}}"), now) var m1Wrap []map[string]any if err := json.Unmarshal([]byte(s1), &m1Wrap); err != nil { t.Fatal(err) @@ -185,7 +185,7 @@ func TestJSONFormatWithNoUpdateConfig(t *testing.T) { } func TestPrettyPrintWithConfigsAndSecrets(t *testing.T) { - s := formatServiceInspect(t, NewFormat("pretty"), time.Now()) + s := formatServiceInspect(t, newFormat("pretty"), time.Now()) assert.Check(t, is.Contains(s, "Log Driver:"), "Pretty print missing Log Driver") assert.Check(t, is.Contains(s, "Configs:"), "Pretty print missing configs") assert.Check(t, is.Contains(s, "Secrets:"), "Pretty print missing secrets") From 0e17907266ba5c501e68284050f91bb24891de10 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 21 Aug 2025 15:09:44 +0200 Subject: [PATCH 096/193] cli/command/task: deprecate NewTaskFormat, FormatWrite It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit c3ee82fdc325d373e47b6cae733198334db492ff) Signed-off-by: Sebastiaan van Stijn --- cli/command/task/formatter.go | 20 +++++++++++++++++--- cli/command/task/formatter_test.go | 14 +++++++------- cli/command/task/print.go | 4 ++-- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/cli/command/task/formatter.go b/cli/command/task/formatter.go index b87ab3a4b715..aba09f9d1928 100644 --- a/cli/command/task/formatter.go +++ b/cli/command/task/formatter.go @@ -23,7 +23,14 @@ const ( ) // NewTaskFormat returns a Format for rendering using a task Context +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewTaskFormat(source string, quiet bool) formatter.Format { + return newTaskFormat(source, quiet) +} + +// newTaskFormat returns a Format for rendering using a taskContext. +func newTaskFormat(source string, quiet bool) formatter.Format { switch source { case formatter.TableFormatKey: if quiet { @@ -40,10 +47,17 @@ func NewTaskFormat(source string, quiet bool) formatter.Format { } // FormatWrite writes the context -func FormatWrite(ctx formatter.Context, tasks []swarm.Task, names map[string]string, nodes map[string]string) error { +// +// Deprecated: this function was only used internally and will be removed in the next release. +func FormatWrite(fmtCtx formatter.Context, tasks []swarm.Task, names map[string]string, nodes map[string]string) error { + return formatWrite(fmtCtx, tasks, names, nodes) +} + +// formatWrite writes the context. +func formatWrite(fmtCtx formatter.Context, tasks []swarm.Task, names map[string]string, nodes map[string]string) error { render := func(format func(subContext formatter.SubContext) error) error { for _, task := range tasks { - taskCtx := &taskContext{trunc: ctx.Trunc, task: task, name: names[task.ID], node: nodes[task.ID]} + taskCtx := &taskContext{trunc: fmtCtx.Trunc, task: task, name: names[task.ID], node: nodes[task.ID]} if err := format(taskCtx); err != nil { return err } @@ -61,7 +75,7 @@ func FormatWrite(ctx formatter.Context, tasks []swarm.Task, names map[string]str "Error": formatter.ErrorHeader, "Ports": formatter.PortsHeader, } - return ctx.Write(&taskCtx, render) + return fmtCtx.Write(&taskCtx, render) } type taskContext struct { diff --git a/cli/command/task/formatter_test.go b/cli/command/task/formatter_test.go index c857c64bb4eb..4d7ec9ee99b7 100644 --- a/cli/command/task/formatter_test.go +++ b/cli/command/task/formatter_test.go @@ -27,30 +27,30 @@ func TestTaskContextWrite(t *testing.T) { `template parsing error: template: :1:2: executing "" at : nil is not a command`, }, { - formatter.Context{Format: NewTaskFormat("table", true)}, + formatter.Context{Format: newTaskFormat("table", true)}, `taskID1 taskID2 `, }, { - formatter.Context{Format: NewTaskFormat("table {{.Name}}\t{{.Node}}\t{{.Ports}}", false)}, + formatter.Context{Format: newTaskFormat("table {{.Name}}\t{{.Node}}\t{{.Ports}}", false)}, string(golden.Get(t, "task-context-write-table-custom.golden")), }, { - formatter.Context{Format: NewTaskFormat("table {{.Name}}", true)}, + formatter.Context{Format: newTaskFormat("table {{.Name}}", true)}, `NAME foobar_baz foobar_bar `, }, { - formatter.Context{Format: NewTaskFormat("raw", true)}, + formatter.Context{Format: newTaskFormat("raw", true)}, `id: taskID1 id: taskID2 `, }, { - formatter.Context{Format: NewTaskFormat("{{.Name}} {{.Node}}", false)}, + formatter.Context{Format: newTaskFormat("{{.Name}} {{.Node}}", false)}, `foobar_baz foo1 foobar_bar foo2 `, @@ -75,7 +75,7 @@ foobar_bar foo2 var out bytes.Buffer tc.context.Output = &out - if err := FormatWrite(tc.context, tasks, names, nodes); err != nil { + if err := formatWrite(tc.context, tasks, names, nodes); err != nil { assert.Error(t, err, tc.expected) } else { assert.Equal(t, out.String(), tc.expected) @@ -94,7 +94,7 @@ func TestTaskContextWriteJSONField(t *testing.T) { "taskID2": "foobar_bar", } out := bytes.NewBufferString("") - err := FormatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, tasks, names, map[string]string{}) + err := formatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, tasks, names, map[string]string{}) if err != nil { t.Fatal(err) } diff --git a/cli/command/task/print.go b/cli/command/task/print.go index 4c9b362a2fb3..90b6bb967cbe 100644 --- a/cli/command/task/print.go +++ b/cli/command/task/print.go @@ -50,7 +50,7 @@ func Print(ctx context.Context, dockerCli command.Cli, tasks []swarm.Task, resol tasksCtx := formatter.Context{ Output: dockerCli.Out(), - Format: NewTaskFormat(format, quiet), + Format: newTaskFormat(format, quiet), Trunc: trunc, } @@ -75,7 +75,7 @@ func Print(ctx context.Context, dockerCli command.Cli, tasks []swarm.Task, resol nodes[task.ID] = nodeValue } - return FormatWrite(tasksCtx, tasks, names, nodes) + return formatWrite(tasksCtx, tasks, names, nodes) } // generateTaskNames generates names for the given tasks, and returns a copy of From 41332715308e106b250bc70bb018229edc804453 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 21 Aug 2025 15:21:29 +0200 Subject: [PATCH 097/193] cli/command/trust: deprecate formatting-related functions and types It's part of the presentation logic of the cli, and only used internally. We can consider providing utilities for these, but better as part of separate packages. This deprecates the following types and functions: - `SignedTagInfo` - `SignerInfo` - `NewTrustTagFormat` - `NewSignerInfoFormat` - `TagWrite` - `SignerInfoWrite` Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 95c9b1b13bfd064d1d6d4f2a637862c0564ac237) Signed-off-by: Sebastiaan van Stijn --- cli/command/trust/formatter.go | 51 ++++++++++++++++++++++++----- cli/command/trust/formatter_test.go | 38 ++++++++++----------- cli/command/trust/inspect_pretty.go | 16 ++++----- 3 files changed, 69 insertions(+), 36 deletions(-) diff --git a/cli/command/trust/formatter.go b/cli/command/trust/formatter.go index 9597cfbd7697..e536995fd711 100644 --- a/cli/command/trust/formatter.go +++ b/cli/command/trust/formatter.go @@ -21,7 +21,15 @@ const ( // Name: name of the signed tag // Digest: hex encoded digest of the contents // Signers: list of entities who signed the tag -type SignedTagInfo struct { +// +// Deprecated: this type was only used internally and will be removed in the next release. +type SignedTagInfo = signedTagInfo + +// signedTagInfo represents all formatted information needed to describe a signed tag: +// Name: name of the signed tag +// Digest: hex encoded digest of the contents +// Signers: list of entities who signed the tag +type signedTagInfo struct { Name string Digest string Signers []string @@ -30,23 +38,41 @@ type SignedTagInfo struct { // SignerInfo represents all formatted information needed to describe a signer: // Name: name of the signer role // Keys: the keys associated with the signer -type SignerInfo struct { +// +// Deprecated: this type was only used internally and will be removed in the next release. +type SignerInfo = signerInfo + +// signerInfo represents all formatted information needed to describe a signer: +// Name: name of the signer role +// Keys: the keys associated with the signer +type signerInfo struct { Name string Keys []string } // NewTrustTagFormat returns a Format for rendering using a trusted tag Context +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewTrustTagFormat() formatter.Format { return defaultTrustTagTableFormat } // NewSignerInfoFormat returns a Format for rendering a signer role info Context +// +// Deprecated: this function was only used internally and will be removed in the next release. func NewSignerInfoFormat() formatter.Format { return defaultSignerInfoTableFormat } // TagWrite writes the context -func TagWrite(ctx formatter.Context, signedTagInfoList []SignedTagInfo) error { +// +// Deprecated: this function was only used internally and will be removed in the next release. +func TagWrite(fmtCtx formatter.Context, signedTagInfoList []signedTagInfo) error { + return tagWrite(fmtCtx, signedTagInfoList) +} + +// tagWrite writes the context +func tagWrite(fmtCtx formatter.Context, signedTagInfoList []signedTagInfo) error { render := func(format func(subContext formatter.SubContext) error) error { for _, signedTag := range signedTagInfoList { if err := format(&trustTagContext{s: signedTag}); err != nil { @@ -61,12 +87,12 @@ func TagWrite(ctx formatter.Context, signedTagInfoList []SignedTagInfo) error { "Digest": trustedDigestHeader, "Signers": signersHeader, } - return ctx.Write(&trustTagCtx, render) + return fmtCtx.Write(&trustTagCtx, render) } type trustTagContext struct { formatter.HeaderContext - s SignedTagInfo + s signedTagInfo } // SignedTag returns the name of the signed tag @@ -86,11 +112,18 @@ func (c *trustTagContext) Signers() string { } // SignerInfoWrite writes the context -func SignerInfoWrite(ctx formatter.Context, signerInfoList []SignerInfo) error { +// +// Deprecated: this function was only used internally and will be removed in the next release. +func SignerInfoWrite(fmtCtx formatter.Context, signerInfoList []signerInfo) error { + return signerInfoWrite(fmtCtx, signerInfoList) +} + +// signerInfoWrite writes the context. +func signerInfoWrite(fmtCtx formatter.Context, signerInfoList []signerInfo) error { render := func(format func(subContext formatter.SubContext) error) error { for _, signerInfo := range signerInfoList { if err := format(&signerInfoContext{ - trunc: ctx.Trunc, + trunc: fmtCtx.Trunc, s: signerInfo, }); err != nil { return err @@ -103,13 +136,13 @@ func SignerInfoWrite(ctx formatter.Context, signerInfoList []SignerInfo) error { "Signer": signerNameHeader, "Keys": keysHeader, } - return ctx.Write(&signerInfoCtx, render) + return fmtCtx.Write(&signerInfoCtx, render) } type signerInfoContext struct { formatter.HeaderContext trunc bool - s SignerInfo + s signerInfo } // Keys returns the sorted list of keys associated with the signer diff --git a/cli/command/trust/formatter_test.go b/cli/command/trust/formatter_test.go index 4c0c194f4c2a..413a2605aba4 100644 --- a/cli/command/trust/formatter_test.go +++ b/cli/command/trust/formatter_test.go @@ -23,7 +23,7 @@ func TestTrustTag(t *testing.T) { }{ { trustTagContext{ - s: SignedTagInfo{ + s: signedTagInfo{ Name: trustedTag, Digest: digest, Signers: nil, @@ -34,7 +34,7 @@ func TestTrustTag(t *testing.T) { }, { trustTagContext{ - s: SignedTagInfo{ + s: signedTagInfo{ Name: trustedTag, Digest: digest, Signers: nil, @@ -46,7 +46,7 @@ func TestTrustTag(t *testing.T) { // Empty signers makes a row with empty string { trustTagContext{ - s: SignedTagInfo{ + s: signedTagInfo{ Name: trustedTag, Digest: digest, Signers: nil, @@ -57,7 +57,7 @@ func TestTrustTag(t *testing.T) { }, { trustTagContext{ - s: SignedTagInfo{ + s: signedTagInfo{ Name: trustedTag, Digest: digest, Signers: []string{"alice", "bob", "claire"}, @@ -69,7 +69,7 @@ func TestTrustTag(t *testing.T) { // alphabetic signing on Signers { trustTagContext{ - s: SignedTagInfo{ + s: signedTagInfo{ Name: trustedTag, Digest: digest, Signers: []string{"claire", "bob", "alice"}, @@ -110,7 +110,7 @@ func TestTrustTagContextWrite(t *testing.T) { // Table Format { formatter.Context{ - Format: NewTrustTagFormat(), + Format: defaultTrustTagTableFormat, }, `SIGNED TAG DIGEST SIGNERS tag1 deadbeef alice @@ -120,7 +120,7 @@ tag3 bbbbbbbb }, } - signedTags := []SignedTagInfo{ + signedTags := []signedTagInfo{ {Name: "tag1", Digest: "deadbeef", Signers: []string{"alice"}}, {Name: "tag2", Digest: "aaaaaaaa", Signers: []string{"alice", "bob"}}, {Name: "tag3", Digest: "bbbbbbbb", Signers: []string{}}, @@ -131,7 +131,7 @@ tag3 bbbbbbbb var out bytes.Buffer tc.context.Output = &out - if err := TagWrite(tc.context, signedTags); err != nil { + if err := tagWrite(tc.context, signedTags); err != nil { assert.Error(t, err, tc.expected) } else { assert.Equal(t, out.String(), tc.expected) @@ -140,7 +140,7 @@ tag3 bbbbbbbb } } -// With no trust data, the TagWrite will print an empty table: +// With no trust data, the formatWrite will print an empty table: // it's up to the caller to decide whether or not to print this versus an error func TestTrustTagContextEmptyWrite(t *testing.T) { emptyCase := struct { @@ -148,16 +148,16 @@ func TestTrustTagContextEmptyWrite(t *testing.T) { expected string }{ formatter.Context{ - Format: NewTrustTagFormat(), + Format: defaultTrustTagTableFormat, }, `SIGNED TAG DIGEST SIGNERS `, } - emptySignedTags := []SignedTagInfo{} + emptySignedTags := []signedTagInfo{} out := bytes.NewBufferString("") emptyCase.context.Output = out - err := TagWrite(emptyCase.context, emptySignedTags) + err := tagWrite(emptyCase.context, emptySignedTags) assert.NilError(t, err) assert.Check(t, is.Equal(emptyCase.expected, out.String())) } @@ -168,15 +168,15 @@ func TestSignerInfoContextEmptyWrite(t *testing.T) { expected string }{ formatter.Context{ - Format: NewSignerInfoFormat(), + Format: defaultSignerInfoTableFormat, }, `SIGNER KEYS `, } - emptySignerInfo := []SignerInfo{} + emptySignerInfo := []signerInfo{} out := bytes.NewBufferString("") emptyCase.context.Output = out - err := SignerInfoWrite(emptyCase.context, emptySignerInfo) + err := signerInfoWrite(emptyCase.context, emptySignerInfo) assert.NilError(t, err) assert.Check(t, is.Equal(emptyCase.expected, out.String())) } @@ -202,7 +202,7 @@ func TestSignerInfoContextWrite(t *testing.T) { // Table Format { formatter.Context{ - Format: NewSignerInfoFormat(), + Format: defaultSignerInfoTableFormat, Trunc: true, }, `SIGNER KEYS @@ -214,7 +214,7 @@ eve foobarbazqux, key31, key32 // No truncation { formatter.Context{ - Format: NewSignerInfoFormat(), + Format: defaultSignerInfoTableFormat, }, `SIGNER KEYS alice key11, key12 @@ -224,7 +224,7 @@ eve foobarbazquxquux, key31, key32 }, } - signerInfo := []SignerInfo{ + signerInfo := []signerInfo{ {Name: "alice", Keys: []string{"key11", "key12"}}, {Name: "bob", Keys: []string{"key21"}}, {Name: "eve", Keys: []string{"key31", "key32", "foobarbazquxquux"}}, @@ -234,7 +234,7 @@ eve foobarbazquxquux, key31, key32 var out bytes.Buffer tc.context.Output = &out - if err := SignerInfoWrite(tc.context, signerInfo); err != nil { + if err := signerInfoWrite(tc.context, signerInfo); err != nil { assert.Error(t, err, tc.expected) } else { assert.Equal(t, out.String(), tc.expected) diff --git a/cli/command/trust/inspect_pretty.go b/cli/command/trust/inspect_pretty.go index b0b5e8c51c84..3eac2cfbe827 100644 --- a/cli/command/trust/inspect_pretty.go +++ b/cli/command/trust/inspect_pretty.go @@ -56,33 +56,33 @@ func printSortedAdminKeys(out io.Writer, adminRoles []client.RoleWithSignatures) func printSignatures(out io.Writer, signatureRows []trustTagRow) error { trustTagCtx := formatter.Context{ Output: out, - Format: NewTrustTagFormat(), + Format: defaultTrustTagTableFormat, } // convert the formatted type before printing - formattedTags := []SignedTagInfo{} + formattedTags := []signedTagInfo{} for _, sigRow := range signatureRows { formattedSigners := sigRow.Signers if len(formattedSigners) == 0 { formattedSigners = append(formattedSigners, fmt.Sprintf("(%s)", releasedRoleName)) } - formattedTags = append(formattedTags, SignedTagInfo{ + formattedTags = append(formattedTags, signedTagInfo{ Name: sigRow.SignedTag, Digest: sigRow.Digest, Signers: formattedSigners, }) } - return TagWrite(trustTagCtx, formattedTags) + return tagWrite(trustTagCtx, formattedTags) } func printSignerInfo(out io.Writer, roleToKeyIDs map[string][]string) error { signerInfoCtx := formatter.Context{ Output: out, - Format: NewSignerInfoFormat(), + Format: defaultSignerInfoTableFormat, Trunc: true, } - formattedSignerInfo := []SignerInfo{} + formattedSignerInfo := []signerInfo{} for name, keyIDs := range roleToKeyIDs { - formattedSignerInfo = append(formattedSignerInfo, SignerInfo{ + formattedSignerInfo = append(formattedSignerInfo, signerInfo{ Name: name, Keys: keyIDs, }) @@ -90,5 +90,5 @@ func printSignerInfo(out io.Writer, roleToKeyIDs map[string][]string) error { sort.Slice(formattedSignerInfo, func(i, j int) bool { return sortorder.NaturalLess(formattedSignerInfo[i].Name, formattedSignerInfo[j].Name) }) - return SignerInfoWrite(signerInfoCtx, formattedSignerInfo) + return signerInfoWrite(signerInfoCtx, formattedSignerInfo) } From 1415080cfaa2ba8f2ead3f56d29e9cb8f30095fd Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 22 Aug 2025 12:18:27 +0200 Subject: [PATCH 098/193] cli/command/builder: deprecate NewPruneCommand This patch deprecates exported NewPruneCommand and moves the implementation details to an unexported function. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 7032f5922e4f49648b684b97b700c527fbcd3edf) Signed-off-by: Sebastiaan van Stijn --- cli/command/builder/cmd.go | 2 +- cli/command/builder/prune.go | 13 ++++++++++--- cli/command/builder/prune_test.go | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cli/command/builder/cmd.go b/cli/command/builder/cmd.go index 44d6496397ae..e65bdd3d394b 100644 --- a/cli/command/builder/cmd.go +++ b/cli/command/builder/cmd.go @@ -24,7 +24,7 @@ func newBuilderCommand(dockerCLI command.Cli) *cobra.Command { Annotations: map[string]string{"version": "1.31"}, } cmd.AddCommand( - NewPruneCommand(dockerCLI), + newPruneCommand(dockerCLI), // we should have a mechanism for registering sub-commands in the cli/internal/commands.Register function. //nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283) image.NewBuildCommand(dockerCLI), diff --git a/cli/command/builder/prune.go b/cli/command/builder/prune.go index 7a323a393941..1c36f358edfa 100644 --- a/cli/command/builder/prune.go +++ b/cli/command/builder/prune.go @@ -24,7 +24,14 @@ type pruneOptions struct { } // NewPruneCommand returns a new cobra prune command for images +// +// Deprecated: Do not import commands directly. They will be removed in a future release. func NewPruneCommand(dockerCli command.Cli) *cobra.Command { + return newPruneCommand(dockerCli) +} + +// newPruneCommand returns a new cobra prune command for images +func newPruneCommand(dockerCLI command.Cli) *cobra.Command { options := pruneOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ @@ -32,14 +39,14 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { Short: "Remove build cache", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCli, options) + spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCLI, options) if err != nil { return err } if output != "" { - fmt.Fprintln(dockerCli.Out(), output) + _, _ = fmt.Fprintln(dockerCLI.Out(), output) } - fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) + _, _ = fmt.Fprintln(dockerCLI.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) return nil }, Annotations: map[string]string{"version": "1.39"}, diff --git a/cli/command/builder/prune_test.go b/cli/command/builder/prune_test.go index e0c3097b07e2..de1582cf6a77 100644 --- a/cli/command/builder/prune_test.go +++ b/cli/command/builder/prune_test.go @@ -19,7 +19,7 @@ func TestBuilderPromptTermination(t *testing.T) { return nil, errors.New("fakeClient builderPruneFunc should not be called") }, }) - cmd := NewPruneCommand(cli) + cmd := newPruneCommand(cli) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) test.TerminatePrompt(ctx, t, cmd, cli) From bd3e420cc7903c8a15b47e28b8bc93444b33708d Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 22 Aug 2025 14:26:38 +0200 Subject: [PATCH 099/193] vendor: golang.org/x/sync v0.16.0 Brings in the errgroup implementation for reverted auto-recover from panics. full diff: https://github.com/golang/sync/compare/v0.14.0...v0.16.0 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit c7cbac58b3c585264d58c5bd12d2142c4419deb9) Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 2 +- vendor.sum | 4 +- vendor/golang.org/x/sync/errgroup/errgroup.go | 121 +++++------------- vendor/modules.txt | 2 +- 4 files changed, 36 insertions(+), 93 deletions(-) diff --git a/vendor.mod b/vendor.mod index b0744d0334e8..aab595e5861b 100644 --- a/vendor.mod +++ b/vendor.mod @@ -56,7 +56,7 @@ require ( go.opentelemetry.io/otel/sdk v1.35.0 go.opentelemetry.io/otel/sdk/metric v1.35.0 go.opentelemetry.io/otel/trace v1.35.0 - golang.org/x/sync v0.14.0 + golang.org/x/sync v0.16.0 golang.org/x/sys v0.33.0 golang.org/x/term v0.31.0 golang.org/x/text v0.24.0 diff --git a/vendor.sum b/vendor.sum index 5f58c88f589b..d355a0b36f6f 100644 --- a/vendor.sum +++ b/vendor.sum @@ -348,8 +348,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go index cfafed5b54c6..1d8cffae8cfc 100644 --- a/vendor/golang.org/x/sync/errgroup/errgroup.go +++ b/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -12,8 +12,6 @@ package errgroup import ( "context" "fmt" - "runtime" - "runtime/debug" "sync" ) @@ -33,10 +31,6 @@ type Group struct { errOnce sync.Once err error - - mu sync.Mutex - panicValue any // = PanicError | PanicValue; non-nil if some Group.Go goroutine panicked. - abnormal bool // some Group.Go goroutine terminated abnormally (panic or goexit). } func (g *Group) done() { @@ -56,80 +50,47 @@ func WithContext(ctx context.Context) (*Group, context.Context) { return &Group{cancel: cancel}, ctx } -// Wait blocks until all function calls from the Go method have returned -// normally, then returns the first non-nil error (if any) from them. -// -// If any of the calls panics, Wait panics with a [PanicValue]; -// and if any of them calls [runtime.Goexit], Wait calls runtime.Goexit. +// Wait blocks until all function calls from the Go method have returned, then +// returns the first non-nil error (if any) from them. func (g *Group) Wait() error { g.wg.Wait() if g.cancel != nil { g.cancel(g.err) } - if g.panicValue != nil { - panic(g.panicValue) - } - if g.abnormal { - runtime.Goexit() - } return g.err } // Go calls the given function in a new goroutine. -// The first call to Go must happen before a Wait. -// It blocks until the new goroutine can be added without the number of -// active goroutines in the group exceeding the configured limit. // +// The first call to Go must happen before a Wait. // It blocks until the new goroutine can be added without the number of // goroutines in the group exceeding the configured limit. // -// The first goroutine in the group that returns a non-nil error, panics, or -// invokes [runtime.Goexit] will cancel the associated Context, if any. +// The first goroutine in the group that returns a non-nil error will +// cancel the associated Context, if any. The error will be returned +// by Wait. func (g *Group) Go(f func() error) { if g.sem != nil { g.sem <- token{} } - g.add(f) -} - -func (g *Group) add(f func() error) { g.wg.Add(1) go func() { defer g.done() - normalReturn := false - defer func() { - if normalReturn { - return - } - v := recover() - g.mu.Lock() - defer g.mu.Unlock() - if !g.abnormal { - if g.cancel != nil { - g.cancel(g.err) - } - g.abnormal = true - } - if v != nil && g.panicValue == nil { - switch v := v.(type) { - case error: - g.panicValue = PanicError{ - Recovered: v, - Stack: debug.Stack(), - } - default: - g.panicValue = PanicValue{ - Recovered: v, - Stack: debug.Stack(), - } - } - } - }() - err := f() - normalReturn = true - if err != nil { + // It is tempting to propagate panics from f() + // up to the goroutine that calls Wait, but + // it creates more problems than it solves: + // - it delays panics arbitrarily, + // making bugs harder to detect; + // - it turns f's panic stack into a mere value, + // hiding it from crash-monitoring tools; + // - it risks deadlocks that hide the panic entirely, + // if f's panic leaves the program in a state + // that prevents the Wait call from being reached. + // See #53757, #74275, #74304, #74306. + + if err := f(); err != nil { g.errOnce.Do(func() { g.err = err if g.cancel != nil { @@ -154,7 +115,19 @@ func (g *Group) TryGo(f func() error) bool { } } - g.add(f) + g.wg.Add(1) + go func() { + defer g.done() + + if err := f(); err != nil { + g.errOnce.Do(func() { + g.err = err + if g.cancel != nil { + g.cancel(g.err) + } + }) + } + }() return true } @@ -176,33 +149,3 @@ func (g *Group) SetLimit(n int) { } g.sem = make(chan token, n) } - -// PanicError wraps an error recovered from an unhandled panic -// when calling a function passed to Go or TryGo. -type PanicError struct { - Recovered error - Stack []byte // result of call to [debug.Stack] -} - -func (p PanicError) Error() string { - // A Go Error method conventionally does not include a stack dump, so omit it - // here. (Callers who care can extract it from the Stack field.) - return fmt.Sprintf("recovered from errgroup.Group: %v", p.Recovered) -} - -func (p PanicError) Unwrap() error { return p.Recovered } - -// PanicValue wraps a value that does not implement the error interface, -// recovered from an unhandled panic when calling a function passed to Go or -// TryGo. -type PanicValue struct { - Recovered any - Stack []byte // result of call to [debug.Stack] -} - -func (p PanicValue) String() string { - if len(p.Stack) > 0 { - return fmt.Sprintf("recovered from errgroup.Group: %v\n%s", p.Recovered, p.Stack) - } - return fmt.Sprintf("recovered from errgroup.Group: %v", p.Recovered) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8ccf406adc1f..bc28cd3a0a12 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -414,7 +414,7 @@ golang.org/x/net/idna golang.org/x/net/internal/httpcommon golang.org/x/net/internal/timeseries golang.org/x/net/trace -# golang.org/x/sync v0.14.0 +# golang.org/x/sync v0.16.0 ## explicit; go 1.23.0 golang.org/x/sync/errgroup # golang.org/x/sys v0.33.0 From 82c5138c8eb58ed3a8ef6f1efda734e9f462d794 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sat, 23 Aug 2025 02:17:20 +0200 Subject: [PATCH 100/193] internal/jsonstream: remove uses of deprecated fields Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 045ac0b159e8bddb7d18eff8969fe8985e15947b) Signed-off-by: Sebastiaan van Stijn --- internal/jsonstream/display_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/jsonstream/display_test.go b/internal/jsonstream/display_test.go index 7b14db2a9fac..3a9c118ed813 100644 --- a/internal/jsonstream/display_test.go +++ b/internal/jsonstream/display_test.go @@ -30,10 +30,8 @@ func TestDisplay(t *testing.T) { return default: err := enc.Encode(JSONMessage{ - Status: "Downloading", - ID: fmt.Sprintf("id-%d", i), - TimeNano: time.Now().UnixNano(), - Time: time.Now().Unix(), + Status: "Downloading", + ID: fmt.Sprintf("id-%d", i), Progress: &JSONProgress{ Current: int64(i), Total: 100, From 8def226e5335ab75cf8c45fa92744f97fbfaa350 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 1 Apr 2025 09:25:59 +0200 Subject: [PATCH 101/193] cli/command/image: deprecate AuthResolver and un-export This function was exported to share it between "trust" and "image", but was only a shallow wrapper, so split the implementations where used. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 7ad113ccc2c958f4bd39db9d22783678ccbcb044) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/pull.go | 2 +- cli/command/image/trust.go | 18 +++++++++++++----- cli/command/trust/common.go | 12 ++++++++++-- cli/command/trust/revoke.go | 3 +-- cli/command/trust/sign.go | 3 +-- cli/command/trust/signer_add.go | 3 +-- cli/command/trust/signer_remove.go | 3 +-- 7 files changed, 28 insertions(+), 16 deletions(-) diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index 54f168b049b5..aa1c26afd866 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -85,7 +85,7 @@ func runPull(ctx context.Context, dockerCLI command.Cli, opts pullOptions) error } } - imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, AuthResolver(dockerCLI), distributionRef.String()) + imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver(dockerCLI), distributionRef.String()) if err != nil { return err } diff --git a/cli/command/image/trust.go b/cli/command/image/trust.go index 00268e7592da..58c69a9bf719 100644 --- a/cli/command/image/trust.go +++ b/cli/command/image/trust.go @@ -8,6 +8,7 @@ import ( "github.com/distribution/reference" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/streams" "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/jsonstream" @@ -68,7 +69,7 @@ func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.Image if err != nil { return err } - updatedImgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, AuthResolver(cli), trustedRef.String()) + updatedImgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver(cli), trustedRef.String()) if err != nil { return err } @@ -172,7 +173,7 @@ func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth tru // TrustedReference returns the canonical trusted reference for an image reference func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedTagged) (reference.Canonical, error) { - imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, AuthResolver(cli), ref.String()) + imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver(cli), ref.String()) if err != nil { return nil, err } @@ -210,9 +211,16 @@ func convertTarget(t client.Target) (target, error) { }, nil } -// AuthResolver returns an auth resolver function from a command.Cli -func AuthResolver(cli command.Cli) func(ctx context.Context, index *registrytypes.IndexInfo) registrytypes.AuthConfig { +// AuthResolver returns an auth resolver function from a [config.Provider]. +// +// Deprecated: this function was only used internally and will be removed in the next release. +func AuthResolver(dockerCLI config.Provider) func(ctx context.Context, index *registrytypes.IndexInfo) registrytypes.AuthConfig { + return authResolver(dockerCLI) +} + +// authResolver returns an auth resolver function from a [config.Provider]. +func authResolver(dockerCLI config.Provider) func(ctx context.Context, index *registrytypes.IndexInfo) registrytypes.AuthConfig { return func(ctx context.Context, index *registrytypes.IndexInfo) registrytypes.AuthConfig { - return command.ResolveAuthConfig(cli.ConfigFile(), index) + return command.ResolveAuthConfig(dockerCLI.ConfigFile(), index) } } diff --git a/cli/command/trust/common.go b/cli/command/trust/common.go index 7d1d50e29688..ab8a39c06507 100644 --- a/cli/command/trust/common.go +++ b/cli/command/trust/common.go @@ -8,8 +8,9 @@ import ( "strings" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/image" + "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/trust" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/fvbommel/sortorder" "github.com/sirupsen/logrus" "github.com/theupdateframework/notary" @@ -66,7 +67,7 @@ func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth, a // lookupTrustInfo returns processed signature and role information about a notary repository. // This information is to be pretty printed or serialized into a machine-readable format. func lookupTrustInfo(ctx context.Context, cli command.Cli, remote string) ([]trustTagRow, []client.RoleWithSignatures, []data.Role, error) { - imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), remote) + imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver(cli), remote) if err != nil { return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, err } @@ -167,3 +168,10 @@ func matchReleasedSignatures(allTargets []client.TargetSignedStruct) []trustTagR }) return signatureRows } + +// authResolver returns an auth resolver function from a [config.Provider]. +func authResolver(dockerCLI config.Provider) func(ctx context.Context, index *registrytypes.IndexInfo) registrytypes.AuthConfig { + return func(ctx context.Context, index *registrytypes.IndexInfo) registrytypes.AuthConfig { + return command.ResolveAuthConfig(dockerCLI.ConfigFile(), index) + } +} diff --git a/cli/command/trust/revoke.go b/cli/command/trust/revoke.go index 303c7b8c2d68..63096ada0865 100644 --- a/cli/command/trust/revoke.go +++ b/cli/command/trust/revoke.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/prompt" "github.com/pkg/errors" @@ -35,7 +34,7 @@ func newRevokeCommand(dockerCLI command.Cli) *cobra.Command { } func revokeTrust(ctx context.Context, dockerCLI command.Cli, remote string, options revokeOptions) error { - imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(dockerCLI), remote) + imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver(dockerCLI), remote) if err != nil { return err } diff --git a/cli/command/trust/sign.go b/cli/command/trust/sign.go index e03b9cac7fc6..c2ecd0fbbeb3 100644 --- a/cli/command/trust/sign.go +++ b/cli/command/trust/sign.go @@ -11,7 +11,6 @@ import ( "github.com/distribution/reference" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/trust" imagetypes "github.com/docker/docker/api/types/image" registrytypes "github.com/docker/docker/api/types/registry" @@ -45,7 +44,7 @@ func newSignCommand(dockerCLI command.Cli) *cobra.Command { func runSignImage(ctx context.Context, dockerCLI command.Cli, options signOptions) error { imageName := options.imageName - imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(dockerCLI), imageName) + imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver(dockerCLI), imageName) if err != nil { return err } diff --git a/cli/command/trust/signer_add.go b/cli/command/trust/signer_add.go index 155347074190..c641d61927ee 100644 --- a/cli/command/trust/signer_add.go +++ b/cli/command/trust/signer_add.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/lazyregexp" "github.com/docker/cli/opts" @@ -80,7 +79,7 @@ func addSigner(ctx context.Context, dockerCLI command.Cli, options signerAddOpti } func addSignerToRepo(ctx context.Context, dockerCLI command.Cli, signerName string, repoName string, signerPubKeys []data.PublicKey) error { - imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(dockerCLI), repoName) + imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver(dockerCLI), repoName) if err != nil { return err } diff --git a/cli/command/trust/signer_remove.go b/cli/command/trust/signer_remove.go index 10d2c2933bad..ea9c1fe31355 100644 --- a/cli/command/trust/signer_remove.go +++ b/cli/command/trust/signer_remove.go @@ -7,7 +7,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/prompt" "github.com/pkg/errors" @@ -91,7 +90,7 @@ func maybePromptForSignerRemoval(ctx context.Context, dockerCLI command.Cli, rep // removeSingleSigner attempts to remove a single signer and returns whether signer removal happened. // The signer not being removed doesn't necessarily raise an error e.g. user choosing "No" when prompted for confirmation. func removeSingleSigner(ctx context.Context, dockerCLI command.Cli, repoName, signerName string, forceYes bool) (bool, error) { - imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(dockerCLI), repoName) + imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver(dockerCLI), repoName) if err != nil { return false, err } From 146f6492c7b2858f25d3e76deaba917d15e4c898 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sat, 23 Aug 2025 03:35:51 +0200 Subject: [PATCH 102/193] cli/command/image: remove exported RunPull, PullOptions These were exported in 812f1136850f6c18bbe3f1b2a960a8ff8a8413f3, but while the function and options are exported, the option-fields were all un-exported, so these were not usable. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 9216f04eb6a3c4195cf166acf8aec0aa8559a44e) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/pull.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index 54f168b049b5..4d5986c9437a 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -14,9 +14,6 @@ import ( "github.com/spf13/cobra" ) -// PullOptions defines what and how to pull -type PullOptions = pullOptions - // pullOptions defines what and how to pull. type pullOptions struct { remote string @@ -65,11 +62,6 @@ func newPullCommand(dockerCLI command.Cli) *cobra.Command { return cmd } -// RunPull performs a pull against the engine based on the specified options -func RunPull(ctx context.Context, dockerCLI command.Cli, opts PullOptions) error { - return runPull(ctx, dockerCLI, opts) -} - // runPull performs a pull against the engine based on the specified options func runPull(ctx context.Context, dockerCLI command.Cli, opts pullOptions) error { distributionRef, err := reference.ParseNormalizedNamed(opts.remote) From e60b20f08ea575da95901f2b082015df1fa13356 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 25 Aug 2025 18:06:34 +0200 Subject: [PATCH 103/193] cli/command/config: deprecate exported types and functions These were exported in f60369dfe6b5abba1f3b9751234d5233eb2dfbbb to be used in docker enterprise, but this never happened, and there's no known consumers of these, so we should deprecate these. External consumers can still call the API-client directly, which should've been the correct thing to do in the first place. This deprecates: - `RunConfigCreate` and `CreateOptions` - `RunConfigInspect` and `InspectOptions` - `RunConfigList` and `ListOptions` - `RunConfigRemove` and `RemoveOptions` Signed-off-by: Sebastiaan van Stijn (cherry picked from commit a5f4ba08d941df6f3eb768683cbe412ef12487e3) Signed-off-by: Sebastiaan van Stijn --- cli/command/config/create.go | 52 +++++++++++++++++++++++++---------- cli/command/config/inspect.go | 45 +++++++++++++++++++++--------- cli/command/config/ls.go | 40 ++++++++++++++++++++------- cli/command/config/remove.go | 20 +++++++++----- 4 files changed, 112 insertions(+), 45 deletions(-) diff --git a/cli/command/config/create.go b/cli/command/config/create.go index 8fefd19d5926..2bbe1cf61367 100644 --- a/cli/command/config/create.go +++ b/cli/command/config/create.go @@ -16,6 +16,8 @@ import ( ) // CreateOptions specifies some options that are used when creating a config. +// +// Deprecated: this type was for internal use and will be removed in the next release. type CreateOptions struct { Name string TemplateDriver string @@ -23,9 +25,17 @@ type CreateOptions struct { Labels opts.ListOpts } -func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command { - createOpts := CreateOptions{ - Labels: opts.NewListOpts(opts.ValidateLabel), +// createOptions specifies some options that are used when creating a config. +type createOptions struct { + name string + templateDriver string + file string + labels opts.ListOpts +} + +func newConfigCreateCommand(dockerCLI command.Cli) *cobra.Command { + createOpts := createOptions{ + labels: opts.NewListOpts(opts.ValidateLabel), } cmd := &cobra.Command{ @@ -33,39 +43,51 @@ func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command { Short: "Create a config from a file or STDIN", Args: cli.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - createOpts.Name = args[0] - createOpts.File = args[1] - return RunConfigCreate(cmd.Context(), dockerCli, createOpts) + createOpts.name = args[0] + createOpts.file = args[1] + return runCreate(cmd.Context(), dockerCLI, createOpts) }, ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() - flags.VarP(&createOpts.Labels, "label", "l", "Config labels") - flags.StringVar(&createOpts.TemplateDriver, "template-driver", "", "Template driver") - flags.SetAnnotation("template-driver", "version", []string{"1.37"}) + flags.VarP(&createOpts.labels, "label", "l", "Config labels") + flags.StringVar(&createOpts.templateDriver, "template-driver", "", "Template driver") + _ = flags.SetAnnotation("template-driver", "version", []string{"1.37"}) return cmd } // RunConfigCreate creates a config with the given options. +// +// Deprecated: this function was for internal use and will be removed in the next release. func RunConfigCreate(ctx context.Context, dockerCLI command.Cli, options CreateOptions) error { + return runCreate(ctx, dockerCLI, createOptions{ + name: options.Name, + templateDriver: options.TemplateDriver, + file: options.File, + labels: options.Labels, + }) +} + +// runCreate creates a config with the given options. +func runCreate(ctx context.Context, dockerCLI command.Cli, options createOptions) error { apiClient := dockerCLI.Client() - configData, err := readConfigData(dockerCLI.In(), options.File) + configData, err := readConfigData(dockerCLI.In(), options.file) if err != nil { - return errors.Errorf("Error reading content from %q: %v", options.File, err) + return errors.Errorf("Error reading content from %q: %v", options.file, err) } spec := swarm.ConfigSpec{ Annotations: swarm.Annotations{ - Name: options.Name, - Labels: opts.ConvertKVStringsToMap(options.Labels.GetSlice()), + Name: options.name, + Labels: opts.ConvertKVStringsToMap(options.labels.GetSlice()), }, Data: configData, } - if options.TemplateDriver != "" { + if options.templateDriver != "" { spec.Templating = &swarm.Driver{ - Name: options.TemplateDriver, + Name: options.templateDriver, } } r, err := apiClient.ConfigCreate(ctx, spec) diff --git a/cli/command/config/inspect.go b/cli/command/config/inspect.go index d25af89a794b..1b6ab16bc071 100644 --- a/cli/command/config/inspect.go +++ b/cli/command/config/inspect.go @@ -16,57 +16,76 @@ import ( ) // InspectOptions contains options for the docker config inspect command. +// +// Deprecated: this type was for internal use and will be removed in the next release. type InspectOptions struct { Names []string Format string Pretty bool } -func newConfigInspectCommand(dockerCli command.Cli) *cobra.Command { - opts := InspectOptions{} +// inspectOptions contains options for the docker config inspect command. +type inspectOptions struct { + names []string + format string + pretty bool +} + +func newConfigInspectCommand(dockerCLI command.Cli) *cobra.Command { + opts := inspectOptions{} cmd := &cobra.Command{ Use: "inspect [OPTIONS] CONFIG [CONFIG...]", Short: "Display detailed information on one or more configs", Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.Names = args - return RunConfigInspect(cmd.Context(), dockerCli, opts) + opts.names = args + return runInspect(cmd.Context(), dockerCLI, opts) }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return completeNames(dockerCli)(cmd, args, toComplete) + return completeNames(dockerCLI)(cmd, args, toComplete) }, } - cmd.Flags().StringVarP(&opts.Format, "format", "f", "", flagsHelper.InspectFormatHelp) - cmd.Flags().BoolVar(&opts.Pretty, "pretty", false, "Print the information in a human friendly format") + cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) + cmd.Flags().BoolVar(&opts.pretty, "pretty", false, "Print the information in a human friendly format") return cmd } // RunConfigInspect inspects the given Swarm config. +// +// Deprecated: this function was for internal use and will be removed in the next release. func RunConfigInspect(ctx context.Context, dockerCLI command.Cli, opts InspectOptions) error { + return runInspect(ctx, dockerCLI, inspectOptions{ + names: opts.Names, + format: opts.Format, + pretty: opts.Pretty, + }) +} + +// runInspect inspects the given Swarm config. +func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error { apiClient := dockerCLI.Client() - if opts.Pretty { - opts.Format = "pretty" + if opts.pretty { + opts.format = "pretty" } getRef := func(id string) (any, []byte, error) { return apiClient.ConfigInspectWithRaw(ctx, id) } - f := opts.Format // check if the user is trying to apply a template to the pretty format, which // is not supported - if strings.HasPrefix(f, "pretty") && f != "pretty" { + if strings.HasPrefix(opts.format, "pretty") && opts.format != "pretty" { return errors.New("cannot supply extra formatting options to the pretty template") } configCtx := formatter.Context{ Output: dockerCLI.Out(), - Format: newFormat(f, false), + Format: newFormat(opts.format, false), } - if err := inspectFormatWrite(configCtx, opts.Names, getRef); err != nil { + if err := inspectFormatWrite(configCtx, opts.names, getRef); err != nil { return cli.StatusError{StatusCode: 1, Status: err.Error()} } return nil diff --git a/cli/command/config/ls.go b/cli/command/config/ls.go index c3e629c9973e..63ffdae4de48 100644 --- a/cli/command/config/ls.go +++ b/cli/command/config/ls.go @@ -16,14 +16,23 @@ import ( ) // ListOptions contains options for the docker config ls command. +// +// Deprecated: this type was for internal use and will be removed in the next release. type ListOptions struct { Quiet bool Format string Filter opts.FilterOpt } -func newConfigListCommand(dockerCli command.Cli) *cobra.Command { - listOpts := ListOptions{Filter: opts.NewFilterOpt()} +// listOptions contains options for the docker config ls command. +type listOptions struct { + quiet bool + format string + filter opts.FilterOpt +} + +func newConfigListCommand(dockerCLI command.Cli) *cobra.Command { + listOpts := listOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ls [OPTIONS]", @@ -31,31 +40,42 @@ func newConfigListCommand(dockerCli command.Cli) *cobra.Command { Short: "List configs", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return RunConfigList(cmd.Context(), dockerCli, listOpts) + return runList(cmd.Context(), dockerCLI, listOpts) }, ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() - flags.BoolVarP(&listOpts.Quiet, "quiet", "q", false, "Only display IDs") - flags.StringVar(&listOpts.Format, "format", "", flagsHelper.FormatHelp) - flags.VarP(&listOpts.Filter, "filter", "f", "Filter output based on conditions provided") + flags.BoolVarP(&listOpts.quiet, "quiet", "q", false, "Only display IDs") + flags.StringVar(&listOpts.format, "format", "", flagsHelper.FormatHelp) + flags.VarP(&listOpts.filter, "filter", "f", "Filter output based on conditions provided") return cmd } // RunConfigList lists Swarm configs. +// +// Deprecated: this function was for internal use and will be removed in the next release. func RunConfigList(ctx context.Context, dockerCLI command.Cli, options ListOptions) error { + return runList(ctx, dockerCLI, listOptions{ + quiet: options.Quiet, + format: options.Format, + filter: options.Filter, + }) +} + +// runList lists Swarm configs. +func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) error { apiClient := dockerCLI.Client() - configs, err := apiClient.ConfigList(ctx, swarm.ConfigListOptions{Filters: options.Filter.Value()}) + configs, err := apiClient.ConfigList(ctx, swarm.ConfigListOptions{Filters: options.filter.Value()}) if err != nil { return err } - format := options.Format + format := options.format if len(format) == 0 { - if len(dockerCLI.ConfigFile().ConfigFormat) > 0 && !options.Quiet { + if len(dockerCLI.ConfigFile().ConfigFormat) > 0 && !options.quiet { format = dockerCLI.ConfigFile().ConfigFormat } else { format = formatter.TableFormatKey @@ -68,7 +88,7 @@ func RunConfigList(ctx context.Context, dockerCLI command.Cli, options ListOptio configCtx := formatter.Context{ Output: dockerCLI.Out(), - Format: newFormat(format, options.Quiet), + Format: newFormat(format, options.quiet), } return formatWrite(configCtx, configs) } diff --git a/cli/command/config/remove.go b/cli/command/config/remove.go index 01cbe331c135..e74a2722475f 100644 --- a/cli/command/config/remove.go +++ b/cli/command/config/remove.go @@ -11,34 +11,40 @@ import ( ) // RemoveOptions contains options for the docker config rm command. +// +// Deprecated: this type was for internal use and will be removed in the next release. type RemoveOptions struct { Names []string } -func newConfigRemoveCommand(dockerCli command.Cli) *cobra.Command { +func newConfigRemoveCommand(dockerCLI command.Cli) *cobra.Command { return &cobra.Command{ Use: "rm CONFIG [CONFIG...]", Aliases: []string{"remove"}, Short: "Remove one or more configs", Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts := RemoveOptions{ - Names: args, - } - return RunConfigRemove(cmd.Context(), dockerCli, opts) + return runRemove(cmd.Context(), dockerCLI, args) }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return completeNames(dockerCli)(cmd, args, toComplete) + return completeNames(dockerCLI)(cmd, args, toComplete) }, } } // RunConfigRemove removes the given Swarm configs. +// +// Deprecated: this function was for internal use and will be removed in the next release. func RunConfigRemove(ctx context.Context, dockerCLI command.Cli, opts RemoveOptions) error { + return runRemove(ctx, dockerCLI, opts.Names) +} + +// runRemove removes the given Swarm configs. +func runRemove(ctx context.Context, dockerCLI command.Cli, names []string) error { apiClient := dockerCLI.Client() var errs []error - for _, name := range opts.Names { + for _, name := range names { if err := apiClient.ConfigRemove(ctx, name); err != nil { errs = append(errs, err) continue From 1468bb19628201b97a19c143686ad45b437c4a51 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 27 Aug 2025 13:45:05 +0200 Subject: [PATCH 104/193] cli/command/system: TestEventsFormat: remove use of deprecated fields These were just testing JSON marshaling fields that are deprecated, but may be present in a response; these fields will be removed in future API versions, so stop testing for them. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 823c6a75b3ba4f0bb553504ee47f6a1ca43bd31a) Signed-off-by: Sebastiaan van Stijn --- cli/command/system/events_test.go | 3 --- .../system/testdata/docker-events-json-template.golden | 8 ++++---- cli/command/system/testdata/docker-events-json.golden | 8 ++++---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/cli/command/system/events_test.go b/cli/command/system/events_test.go index bf00acf938c4..b31ed3098a4d 100644 --- a/cli/command/system/events_test.go +++ b/cli/command/system/events_test.go @@ -18,9 +18,6 @@ func TestEventsFormat(t *testing.T) { var evts []events.Message //nolint:prealloc for i, action := range []events.Action{events.ActionCreate, events.ActionStart, events.ActionAttach, events.ActionDie} { evts = append(evts, events.Message{ - Status: string(action), - ID: "abc123", - From: "ubuntu:latest", Type: events.ContainerEventType, Action: action, Actor: events.Actor{ diff --git a/cli/command/system/testdata/docker-events-json-template.golden b/cli/command/system/testdata/docker-events-json-template.golden index ec5343fe682e..1a0427163cf0 100644 --- a/cli/command/system/testdata/docker-events-json-template.golden +++ b/cli/command/system/testdata/docker-events-json-template.golden @@ -1,4 +1,4 @@ -{"status":"create","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"create","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":1000000000,"timeNano":1000000000} -{"status":"start","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"start","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":2000000000,"timeNano":2000000000} -{"status":"attach","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"attach","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":3000000000,"timeNano":3000000000} -{"status":"die","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"die","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":4000000000,"timeNano":4000000000} +{"Type":"container","Action":"create","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":1000000000,"timeNano":1000000000} +{"Type":"container","Action":"start","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":2000000000,"timeNano":2000000000} +{"Type":"container","Action":"attach","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":3000000000,"timeNano":3000000000} +{"Type":"container","Action":"die","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":4000000000,"timeNano":4000000000} diff --git a/cli/command/system/testdata/docker-events-json.golden b/cli/command/system/testdata/docker-events-json.golden index ec5343fe682e..1a0427163cf0 100644 --- a/cli/command/system/testdata/docker-events-json.golden +++ b/cli/command/system/testdata/docker-events-json.golden @@ -1,4 +1,4 @@ -{"status":"create","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"create","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":1000000000,"timeNano":1000000000} -{"status":"start","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"start","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":2000000000,"timeNano":2000000000} -{"status":"attach","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"attach","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":3000000000,"timeNano":3000000000} -{"status":"die","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"die","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":4000000000,"timeNano":4000000000} +{"Type":"container","Action":"create","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":1000000000,"timeNano":1000000000} +{"Type":"container","Action":"start","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":2000000000,"timeNano":2000000000} +{"Type":"container","Action":"attach","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":3000000000,"timeNano":3000000000} +{"Type":"container","Action":"die","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":4000000000,"timeNano":4000000000} From 44c1d5775d5481c9ef5567e847ab7d03dad752a2 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 27 Aug 2025 14:42:18 +0200 Subject: [PATCH 105/193] cli/command/system: don't use deprecated fields in test This only impacts the JSON marshaled output; the "regular" output of `docker info` already ignores these fields. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 3d87aa441fb69285a262aeef36544e5d1b5378a7) Signed-off-by: Sebastiaan van Stijn --- cli/command/system/info_test.go | 9 +++------ .../system/testdata/docker-info-badsec.json.golden | 2 +- .../testdata/docker-info-daemon-warnings.json.golden | 2 +- .../system/testdata/docker-info-no-swarm.json.golden | 2 +- .../system/testdata/docker-info-plugins.json.golden | 2 +- .../system/testdata/docker-info-with-devices.json.golden | 2 +- .../system/testdata/docker-info-with-swarm.json.golden | 2 +- 7 files changed, 9 insertions(+), 12 deletions(-) diff --git a/cli/command/system/info_test.go b/cli/command/system/info_test.go index 6b193b7fe921..4ad66839f04a 100644 --- a/cli/command/system/info_test.go +++ b/cli/command/system/info_test.go @@ -109,16 +109,13 @@ var sampleInfoNoSwarm = system.Info{ Isolation: "", InitBinary: "docker-init", ContainerdCommit: system.Commit{ - ID: "6e23458c129b551d5c9871e5174f6b1b7f6d1170", - Expected: "6e23458c129b551d5c9871e5174f6b1b7f6d1170", + ID: "6e23458c129b551d5c9871e5174f6b1b7f6d1170", }, RuncCommit: system.Commit{ - ID: "810190ceaa507aa2727d7ae6f4790c76ec150bd2", - Expected: "810190ceaa507aa2727d7ae6f4790c76ec150bd2", + ID: "810190ceaa507aa2727d7ae6f4790c76ec150bd2", }, InitCommit: system.Commit{ - ID: "949e6fa", - Expected: "949e6fa", + ID: "949e6fa", }, SecurityOptions: []string{"name=apparmor", "name=seccomp,profile=default"}, DefaultAddressPools: []system.NetworkAddressPool{ diff --git a/cli/command/system/testdata/docker-info-badsec.json.golden b/cli/command/system/testdata/docker-info-badsec.json.golden index c20a427f208d..f385cac102e7 100644 --- a/cli/command/system/testdata/docker-info-badsec.json.golden +++ b/cli/command/system/testdata/docker-info-badsec.json.golden @@ -1 +1 @@ -{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"IndexConfigs":{"docker.io":{"Mirrors":null,"Name":"docker.io","Official":true,"Secure":true}},"InsecureRegistryCIDRs":["127.0.0.0/8"],"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["foo="],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ServerErrors":["a server error occurred"],"ClientInfo":{"Debug":false,"Context":"","Plugins":[],"Warnings":null}} +{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"IndexConfigs":{"docker.io":{"Mirrors":null,"Name":"docker.io","Official":true,"Secure":true}},"InsecureRegistryCIDRs":["127.0.0.0/8"],"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["foo="],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ServerErrors":["a server error occurred"],"ClientInfo":{"Debug":false,"Context":"","Plugins":[],"Warnings":null}} diff --git a/cli/command/system/testdata/docker-info-daemon-warnings.json.golden b/cli/command/system/testdata/docker-info-daemon-warnings.json.golden index cdc36eb5dfa8..407404fefe05 100644 --- a/cli/command/system/testdata/docker-info-daemon-warnings.json.golden +++ b/cli/command/system/testdata/docker-info-daemon-warnings.json.golden @@ -1 +1 @@ -{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"IndexConfigs":{"docker.io":{"Mirrors":null,"Name":"docker.io","Official":true,"Secure":true}},"InsecureRegistryCIDRs":["127.0.0.0/8"],"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":["WARNING: No memory limit support","WARNING: No swap limit support","WARNING: No oom kill disable support","WARNING: No cpu cfs quota support","WARNING: No cpu cfs period support","WARNING: No cpu shares support","WARNING: No cpuset support","WARNING: IPv4 forwarding is disabled"],"ClientInfo":{"Debug":true,"Platform":{"Name":"Docker Engine - Community"},"Version":"24.0.0","Context":"default","Plugins":[],"Warnings":null}} +{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"IndexConfigs":{"docker.io":{"Mirrors":null,"Name":"docker.io","Official":true,"Secure":true}},"InsecureRegistryCIDRs":["127.0.0.0/8"],"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":["WARNING: No memory limit support","WARNING: No swap limit support","WARNING: No oom kill disable support","WARNING: No cpu cfs quota support","WARNING: No cpu cfs period support","WARNING: No cpu shares support","WARNING: No cpuset support","WARNING: IPv4 forwarding is disabled"],"ClientInfo":{"Debug":true,"Platform":{"Name":"Docker Engine - Community"},"Version":"24.0.0","Context":"default","Plugins":[],"Warnings":null}} diff --git a/cli/command/system/testdata/docker-info-no-swarm.json.golden b/cli/command/system/testdata/docker-info-no-swarm.json.golden index 7fc58311e8c1..4ad0bad91ea1 100644 --- a/cli/command/system/testdata/docker-info-no-swarm.json.golden +++ b/cli/command/system/testdata/docker-info-no-swarm.json.golden @@ -1 +1 @@ -{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"IndexConfigs":{"docker.io":{"Mirrors":null,"Name":"docker.io","Official":true,"Secure":true}},"InsecureRegistryCIDRs":["127.0.0.0/8"],"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ClientInfo":{"Debug":true,"Platform":{"Name":"Docker Engine - Community"},"Version":"24.0.0","Context":"default","Plugins":[],"Warnings":null}} +{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"IndexConfigs":{"docker.io":{"Mirrors":null,"Name":"docker.io","Official":true,"Secure":true}},"InsecureRegistryCIDRs":["127.0.0.0/8"],"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ClientInfo":{"Debug":true,"Platform":{"Name":"Docker Engine - Community"},"Version":"24.0.0","Context":"default","Plugins":[],"Warnings":null}} diff --git a/cli/command/system/testdata/docker-info-plugins.json.golden b/cli/command/system/testdata/docker-info-plugins.json.golden index d7e39ee90eba..acd05043f49e 100644 --- a/cli/command/system/testdata/docker-info-plugins.json.golden +++ b/cli/command/system/testdata/docker-info-plugins.json.golden @@ -1 +1 @@ -{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"IndexConfigs":{"docker.io":{"Mirrors":null,"Name":"docker.io","Official":true,"Secure":true}},"InsecureRegistryCIDRs":["127.0.0.0/8"],"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","Version":"0.1.0","ShortDescription":"unit test is good","Name":"goodplugin","Path":"/path/to/docker-goodplugin"},{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","ShortDescription":"this plugin has no version","Name":"unversionedplugin","Path":"/path/to/docker-unversionedplugin"},{"Name":"badplugin","Path":"/path/to/docker-badplugin","Err":"something wrong"}],"Warnings":null}} +{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"IndexConfigs":{"docker.io":{"Mirrors":null,"Name":"docker.io","Official":true,"Secure":true}},"InsecureRegistryCIDRs":["127.0.0.0/8"],"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","Version":"0.1.0","ShortDescription":"unit test is good","Name":"goodplugin","Path":"/path/to/docker-goodplugin"},{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","ShortDescription":"this plugin has no version","Name":"unversionedplugin","Path":"/path/to/docker-unversionedplugin"},{"Name":"badplugin","Path":"/path/to/docker-badplugin","Err":"something wrong"}],"Warnings":null}} diff --git a/cli/command/system/testdata/docker-info-with-devices.json.golden b/cli/command/system/testdata/docker-info-with-devices.json.golden index c2fa190b03b9..30ab878c2aa5 100644 --- a/cli/command/system/testdata/docker-info-with-devices.json.golden +++ b/cli/command/system/testdata/docker-info-with-devices.json.golden @@ -1 +1 @@ -{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"IndexConfigs":{"docker.io":{"Mirrors":null,"Name":"docker.io","Official":true,"Secure":true}},"InsecureRegistryCIDRs":["127.0.0.0/8"],"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"DiscoveredDevices":[{"Source":"cdi","ID":"com.example.device1"},{"Source":"cdi","ID":"nvidia.com/gpu=gpu0"}],"Warnings":null} +{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"IndexConfigs":{"docker.io":{"Mirrors":null,"Name":"docker.io","Official":true,"Secure":true}},"InsecureRegistryCIDRs":["127.0.0.0/8"],"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"DiscoveredDevices":[{"Source":"cdi","ID":"com.example.device1"},{"Source":"cdi","ID":"nvidia.com/gpu=gpu0"}],"Warnings":null} diff --git a/cli/command/system/testdata/docker-info-with-swarm.json.golden b/cli/command/system/testdata/docker-info-with-swarm.json.golden index 3351600a6f16..3de5f1388dec 100644 --- a/cli/command/system/testdata/docker-info-with-swarm.json.golden +++ b/cli/command/system/testdata/docker-info-with-swarm.json.golden @@ -1 +1 @@ -{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"IndexConfigs":{"docker.io":{"Mirrors":null,"Name":"docker.io","Official":true,"Secure":true}},"InsecureRegistryCIDRs":["127.0.0.0/8"],"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"qo2dfdig9mmxqkawulggepdih","NodeAddr":"165.227.107.89","LocalNodeState":"active","ControlAvailable":true,"Error":"","RemoteManagers":[{"NodeID":"qo2dfdig9mmxqkawulggepdih","Addr":"165.227.107.89:2377"}],"Nodes":1,"Managers":1,"Cluster":{"ID":"9vs5ygs0gguyyec4iqf2314c0","Version":{"Index":11},"CreatedAt":"2017-08-24T17:34:19.278062352Z","UpdatedAt":"2017-08-24T17:34:42.398815481Z","Spec":{"Name":"default","Labels":null,"Orchestration":{"TaskHistoryRetentionLimit":5},"Raft":{"SnapshotInterval":10000,"KeepOldSnapshots":0,"LogEntriesForSlowFollowers":500,"ElectionTick":3,"HeartbeatTick":1},"Dispatcher":{"HeartbeatPeriod":5000000000},"CAConfig":{"NodeCertExpiry":7776000000000000},"TaskDefaults":{},"EncryptionConfig":{"AutoLockManagers":true}},"TLSInfo":{"TrustRoot":"\n-----BEGIN CERTIFICATE-----\nMIIBajCCARCgAwIBAgIUaFCW5xsq8eyiJ+Pmcv3MCflMLnMwCgYIKoZIzj0EAwIw\nEzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwODI0MTcyOTAwWhcNMzcwODE5MTcy\nOTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH\nA0IABDy7NebyUJyUjWJDBUdnZoV6GBxEGKO4TZPNDwnxDxJcUdLVaB7WGa4/DLrW\nUfsVgh1JGik2VTiLuTMA1tLlNPOjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB\nAf8EBTADAQH/MB0GA1UdDgQWBBQl16XFtaaXiUAwEuJptJlDjfKskDAKBggqhkjO\nPQQDAgNIADBFAiEAo9fTQNM5DP9bHVcTJYfl2Cay1bFu1E+lnpmN+EYJfeACIGKH\n1pCUkZ+D0IB6CiEZGWSHyLuXPM1rlP+I5KuS7sB8\n-----END CERTIFICATE-----\n","CertIssuerSubject":"MBMxETAPBgNVBAMTCHN3YXJtLWNh","CertIssuerPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLs15vJQnJSNYkMFR2dmhXoYHEQYo7hNk80PCfEPElxR0tVoHtYZrj8MutZR+xWCHUkaKTZVOIu5MwDW0uU08w=="},"RootRotationInProgress":false,"DefaultAddrPool":null,"SubnetSize":0,"DataPathPort":0}},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170","Expected":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2","Expected":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa","Expected":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[],"Warnings":null}} +{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"IndexConfigs":{"docker.io":{"Mirrors":null,"Name":"docker.io","Official":true,"Secure":true}},"InsecureRegistryCIDRs":["127.0.0.0/8"],"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"qo2dfdig9mmxqkawulggepdih","NodeAddr":"165.227.107.89","LocalNodeState":"active","ControlAvailable":true,"Error":"","RemoteManagers":[{"NodeID":"qo2dfdig9mmxqkawulggepdih","Addr":"165.227.107.89:2377"}],"Nodes":1,"Managers":1,"Cluster":{"ID":"9vs5ygs0gguyyec4iqf2314c0","Version":{"Index":11},"CreatedAt":"2017-08-24T17:34:19.278062352Z","UpdatedAt":"2017-08-24T17:34:42.398815481Z","Spec":{"Name":"default","Labels":null,"Orchestration":{"TaskHistoryRetentionLimit":5},"Raft":{"SnapshotInterval":10000,"KeepOldSnapshots":0,"LogEntriesForSlowFollowers":500,"ElectionTick":3,"HeartbeatTick":1},"Dispatcher":{"HeartbeatPeriod":5000000000},"CAConfig":{"NodeCertExpiry":7776000000000000},"TaskDefaults":{},"EncryptionConfig":{"AutoLockManagers":true}},"TLSInfo":{"TrustRoot":"\n-----BEGIN CERTIFICATE-----\nMIIBajCCARCgAwIBAgIUaFCW5xsq8eyiJ+Pmcv3MCflMLnMwCgYIKoZIzj0EAwIw\nEzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwODI0MTcyOTAwWhcNMzcwODE5MTcy\nOTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH\nA0IABDy7NebyUJyUjWJDBUdnZoV6GBxEGKO4TZPNDwnxDxJcUdLVaB7WGa4/DLrW\nUfsVgh1JGik2VTiLuTMA1tLlNPOjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB\nAf8EBTADAQH/MB0GA1UdDgQWBBQl16XFtaaXiUAwEuJptJlDjfKskDAKBggqhkjO\nPQQDAgNIADBFAiEAo9fTQNM5DP9bHVcTJYfl2Cay1bFu1E+lnpmN+EYJfeACIGKH\n1pCUkZ+D0IB6CiEZGWSHyLuXPM1rlP+I5KuS7sB8\n-----END CERTIFICATE-----\n","CertIssuerSubject":"MBMxETAPBgNVBAMTCHN3YXJtLWNh","CertIssuerPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLs15vJQnJSNYkMFR2dmhXoYHEQYo7hNk80PCfEPElxR0tVoHtYZrj8MutZR+xWCHUkaKTZVOIu5MwDW0uU08w=="},"RootRotationInProgress":false,"DefaultAddrPool":null,"SubnetSize":0,"DataPathPort":0}},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[],"Warnings":null}} From f385cb994f11d5902283b0914c09515dfb0e25b4 Mon Sep 17 00:00:00 2001 From: Hossein Abbasi <16090309+hsnabszhdn@users.noreply.github.com> Date: Sat, 23 Aug 2025 15:54:56 +1000 Subject: [PATCH 106/193] docs: fix sentence structures in 'run.md' Signed-off-by: Hossein Abbasi <16090309+hsnabszhdn@users.noreply.github.com> (cherry picked from commit ba2c1c94abe626547b13b6c52c6d24186b2b97c5) Signed-off-by: Sebastiaan van Stijn --- docs/reference/run.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/run.md b/docs/reference/run.md index db06ad71f669..0733ad10aa8f 100644 --- a/docs/reference/run.md +++ b/docs/reference/run.md @@ -248,7 +248,7 @@ $ docker run -it --mount type=bind,source=[PATH],target=[PATH] busybox ``` In this case, the `--mount` flag takes three parameters. A type (`bind`), and -two paths. The `source` path is a the location on the host that you want to +two paths. The `source` path is the location on the host that you want to bind mount into the container. The `target` path is the mount destination inside the container. @@ -1087,7 +1087,7 @@ Additionally, you can set any environment variable in the container by using one or more `-e` flags. You can even override the variables mentioned above, or variables defined using a Dockerfile `ENV` instruction when building the image. -If the you name an environment variable without specifying a value, the current +If you name an environment variable without specifying a value, the current value of the named variable on the host is propagated into the container's environment: From c712d97172994675f69609193c9db648d944f706 Mon Sep 17 00:00:00 2001 From: Hossein Abbasi <16090309+hsnabszhdn@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:14:53 +1000 Subject: [PATCH 107/193] docs: add missing backticks in 'run.md' Signed-off-by: Hossein Abbasi <16090309+hsnabszhdn@users.noreply.github.com> (cherry picked from commit d9cafa759f53d3c93a4f6dc771961113251653ea) Signed-off-by: Sebastiaan van Stijn --- docs/reference/run.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/run.md b/docs/reference/run.md index 0733ad10aa8f..9c8b8baf0f25 100644 --- a/docs/reference/run.md +++ b/docs/reference/run.md @@ -419,7 +419,7 @@ $ docker run -it -m 300M ubuntu:24.04 /bin/bash We set memory limit only, this means the processes in the container can use 300M memory and 300M swap memory, by default, the total virtual memory size -(--memory-swap) will be set as double of memory, in this case, memory + swap +(`--memory-swap`) will be set as double of memory, in this case, memory + swap would be 2*300M, so processes can use 300M swap memory as well. ```console From 3b68cde77ac237297f825070bb0959519d3a30b1 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 25 Aug 2025 09:51:48 +0200 Subject: [PATCH 108/193] cli/command/service: fix API annotations for generic resource flags These flags were added in 20a6ff32ee0edaa6b87a493871a9d5e05065e1e9, and require API version v1.32 or up, but they accidentally copied the flag-name from another flag, so were not setting the annotation correctly. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit dcc3d25dc27f8f0c1570d85c679947a29ac1c4c8) Signed-off-by: Sebastiaan van Stijn --- cli/command/service/update.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/command/service/update.go b/cli/command/service/update.go index 2b75b6b59b18..5f9971adf287 100644 --- a/cli/command/service/update.go +++ b/cli/command/service/update.go @@ -112,9 +112,9 @@ func newUpdateCommand(dockerCLI command.Cli) *cobra.Command { // Add needs parsing, Remove only needs the key flags.Var(newListOptsVar(), flagGenericResourcesRemove, "Remove a Generic resource") - flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"}) + flags.SetAnnotation(flagGenericResourcesRemove, "version", []string{"1.32"}) flags.Var(newListOptsVarWithValidator(ValidateSingleGenericResource), flagGenericResourcesAdd, "Add a Generic resource") - flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"}) + flags.SetAnnotation(flagGenericResourcesAdd, "version", []string{"1.32"}) // TODO(thaJeztah): add completion for capabilities, stop-signal (currently non-exported in container package) // _ = cmd.RegisterFlagCompletionFunc(flagCapAdd, completeLinuxCapabilityNames) From e112eeea4d96cee562c56c88058af147127686b7 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 20 Aug 2025 14:55:28 +0200 Subject: [PATCH 109/193] opts: deprecate ParseEnvFile It was a wrapper around kvfile.Load, which should be used instead. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit e650803f091a1e6fc30c5d71b2aae743c22a73db) Signed-off-by: Sebastiaan van Stijn --- {opts => cli/compose/loader}/envfile.go | 14 ++++++++------ {opts => cli/compose/loader}/envfile_test.go | 10 +++++----- cli/compose/loader/loader.go | 2 +- opts/env.go | 11 ++++++----- opts/envfile_deprecated.go | 14 ++++++++++++++ 5 files changed, 34 insertions(+), 17 deletions(-) rename {opts => cli/compose/loader}/envfile.go (54%) rename {opts => cli/compose/loader}/envfile_test.go (79%) create mode 100644 opts/envfile_deprecated.go diff --git a/opts/envfile.go b/cli/compose/loader/envfile.go similarity index 54% rename from opts/envfile.go rename to cli/compose/loader/envfile.go index 3a16e6c189bc..6430f4b5f5f8 100644 --- a/opts/envfile.go +++ b/cli/compose/loader/envfile.go @@ -1,4 +1,4 @@ -package opts +package loader import ( "os" @@ -6,19 +6,21 @@ import ( "github.com/docker/cli/pkg/kvfile" ) -// ParseEnvFile reads a file with environment variables enumerated by lines +// parseEnvFile reads a file with environment variables enumerated by lines // // “Environment variable names used by the utilities in the Shell and -// Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase +// Utilities volume of [IEEE Std 1003.1-2001] consist solely of uppercase // letters, digits, and the '_' (underscore) from the characters defined in // Portable Character Set and do not begin with a digit. *But*, other // characters may be permitted by an implementation; applications shall // tolerate the presence of such names.” -// -- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html // -// As of #16585, it's up to application inside docker to validate or not +// As of [moby-16585], it's up to application inside docker to validate or not // environment variables, that's why we just strip leading whitespace and // nothing more. -func ParseEnvFile(filename string) ([]string, error) { +// +// [IEEE Std 1003.1-2001]: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html +// [moby-16585]: https://github.com/moby/moby/issues/16585 +func parseEnvFile(filename string) ([]string, error) { return kvfile.Parse(filename, os.LookupEnv) } diff --git a/opts/envfile_test.go b/cli/compose/loader/envfile_test.go similarity index 79% rename from opts/envfile_test.go rename to cli/compose/loader/envfile_test.go index 2c01de4fd520..723b1e2269e7 100644 --- a/opts/envfile_test.go +++ b/cli/compose/loader/envfile_test.go @@ -1,4 +1,4 @@ -package opts +package loader import ( "os" @@ -17,13 +17,13 @@ func tmpFileWithContent(t *testing.T, content string) string { return fileName } -// Test ParseEnvFile for a non existent file +// Test parseEnvFile for a non existent file func TestParseEnvFileNonExistentFile(t *testing.T) { - _, err := ParseEnvFile("no_such_file") + _, err := parseEnvFile("no_such_file") assert.Check(t, is.ErrorType(err, os.IsNotExist)) } -// ParseEnvFile with environment variable import definitions +// parseEnvFile with environment variable import definitions func TestParseEnvVariableDefinitionsFile(t *testing.T) { content := `# comment= UNDEFINED_VAR @@ -32,7 +32,7 @@ DEFINED_VAR tmpFile := tmpFileWithContent(t, content) t.Setenv("DEFINED_VAR", "defined-value") - variables, err := ParseEnvFile(tmpFile) + variables, err := parseEnvFile(tmpFile) assert.NilError(t, err) expectedLines := []string{"DEFINED_VAR=defined-value"} diff --git a/cli/compose/loader/loader.go b/cli/compose/loader/loader.go index 839a258f0b6e..3b788ca048f3 100644 --- a/cli/compose/loader/loader.go +++ b/cli/compose/loader/loader.go @@ -468,7 +468,7 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l for _, file := range serviceConfig.EnvFile { filePath := absPath(workingDir, file) - fileVars, err := opts.ParseEnvFile(filePath) + fileVars, err := parseEnvFile(filePath) if err != nil { return err } diff --git a/opts/env.go b/opts/env.go index 675ddda96229..2a21394ba5ba 100644 --- a/opts/env.go +++ b/opts/env.go @@ -7,13 +7,14 @@ import ( ) // ValidateEnv validates an environment variable and returns it. -// If no value is specified, it obtains its value from the current environment +// If no value is specified, it obtains its value from the current environment. // -// As on ParseEnvFile and related to #16585, environment variable names -// are not validated, and it's up to the application inside the container -// to validate them or not. +// Environment variable names are not validated, and it's up to the application +// inside the container to validate them (see [moby-16585]). The only validation +// here is to check if name is empty, per [moby-25099]. // -// The only validation here is to check if name is empty, per #25099 +// [moby-16585]: https://github.com/moby/moby/issues/16585 +// [moby-25099]: https://github.com/moby/moby/issues/25099 func ValidateEnv(val string) (string, error) { k, _, hasValue := strings.Cut(val, "=") if k == "" { diff --git a/opts/envfile_deprecated.go b/opts/envfile_deprecated.go new file mode 100644 index 000000000000..f2f10b1816fe --- /dev/null +++ b/opts/envfile_deprecated.go @@ -0,0 +1,14 @@ +package opts + +import ( + "os" + + "github.com/docker/cli/pkg/kvfile" +) + +// ParseEnvFile reads a file with environment variables enumerated by lines +// +// Deprecated: use [kvfile.Parse] and pass [os.LookupEnv] to lookup env-vars from the current environment. +func ParseEnvFile(filename string) ([]string, error) { + return kvfile.Parse(filename, os.LookupEnv) +} From a27a86289ef7ad7cfd05e6525df7064b950f1b81 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 6 Aug 2025 01:16:17 +0200 Subject: [PATCH 110/193] vendor: dario.cat/mergo v1.0.2 drops gopkg.in/yaml.v3 as dependency full diff: https://github.com/darccio/mergo/compare/v1.0.1...v1.0.2 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit a93ed48d068420bc9de876eaf71de88f1f14f001) Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 2 +- vendor.sum | 4 ++-- vendor/dario.cat/mergo/FUNDING.json | 7 +++++++ vendor/dario.cat/mergo/README.md | 5 ----- vendor/dario.cat/mergo/SECURITY.md | 4 ++-- vendor/modules.txt | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 vendor/dario.cat/mergo/FUNDING.json diff --git a/vendor.mod b/vendor.mod index aab595e5861b..2ceacaab4191 100644 --- a/vendor.mod +++ b/vendor.mod @@ -7,7 +7,7 @@ module github.com/docker/cli go 1.23.0 require ( - dario.cat/mergo v1.0.1 + dario.cat/mergo v1.0.2 github.com/containerd/errdefs v1.0.0 github.com/containerd/log v0.1.0 github.com/containerd/platforms v1.0.0-rc.1 diff --git a/vendor.sum b/vendor.sum index d355a0b36f6f..7c8c04c71f2d 100644 --- a/vendor.sum +++ b/vendor.sum @@ -1,5 +1,5 @@ -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= diff --git a/vendor/dario.cat/mergo/FUNDING.json b/vendor/dario.cat/mergo/FUNDING.json new file mode 100644 index 000000000000..0585e1fe13f7 --- /dev/null +++ b/vendor/dario.cat/mergo/FUNDING.json @@ -0,0 +1,7 @@ +{ + "drips": { + "ethereum": { + "ownedBy": "0x6160020e7102237aC41bdb156e94401692D76930" + } + } +} diff --git a/vendor/dario.cat/mergo/README.md b/vendor/dario.cat/mergo/README.md index 0b3c488893b0..0e4a59afd9ac 100644 --- a/vendor/dario.cat/mergo/README.md +++ b/vendor/dario.cat/mergo/README.md @@ -85,7 +85,6 @@ Mergo is used by [thousands](https://deps.dev/go/dario.cat%2Fmergo/v1.0.0/depend * [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser) * [go-micro/go-micro](https://github.com/go-micro/go-micro) * [grafana/loki](https://github.com/grafana/loki) -* [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) * [masterminds/sprig](github.com/Masterminds/sprig) * [moby/moby](https://github.com/moby/moby) * [slackhq/nebula](https://github.com/slackhq/nebula) @@ -191,10 +190,6 @@ func main() { } ``` -Note: if test are failing due missing package, please execute: - - go get gopkg.in/yaml.v3 - ### Transformers Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`? diff --git a/vendor/dario.cat/mergo/SECURITY.md b/vendor/dario.cat/mergo/SECURITY.md index a5de61f77ba7..3788fcc1c212 100644 --- a/vendor/dario.cat/mergo/SECURITY.md +++ b/vendor/dario.cat/mergo/SECURITY.md @@ -4,8 +4,8 @@ | Version | Supported | | ------- | ------------------ | -| 0.3.x | :white_check_mark: | -| < 0.3 | :x: | +| 1.x.x | :white_check_mark: | +| < 1.0 | :x: | ## Security contact information diff --git a/vendor/modules.txt b/vendor/modules.txt index bc28cd3a0a12..8ecb533542e8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# dario.cat/mergo v1.0.1 +# dario.cat/mergo v1.0.2 ## explicit; go 1.13 dario.cat/mergo # github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c From 48ea3fa14fb19ba25198b4c943dbe64e2c88eb1b Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 27 Aug 2025 15:51:19 +0200 Subject: [PATCH 111/193] vendor: github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 full diff: https://github.com/docker/go-events/compare/e31b211e4f1c...c867878c5e32 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 05220c5f196e48732c8f11b10aabdf67569768ce) Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 2 +- vendor.sum | 4 +-- vendor/github.com/docker/go-events/README.md | 1 - .../github.com/docker/go-events/SECURITY.md | 36 +++++++++++++++++++ vendor/github.com/docker/go-events/vendor.mod | 5 +++ vendor/github.com/docker/go-events/vendor.sum | 16 +++++++++ vendor/modules.txt | 2 +- 7 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 vendor/github.com/docker/go-events/SECURITY.md create mode 100644 vendor/github.com/docker/go-events/vendor.mod create mode 100644 vendor/github.com/docker/go-events/vendor.sum diff --git a/vendor.mod b/vendor.mod index aab595e5861b..7961422e6563 100644 --- a/vendor.mod +++ b/vendor.mod @@ -73,7 +73,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect - github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect + github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect diff --git a/vendor.sum b/vendor.sum index d355a0b36f6f..8c93c7f7ac45 100644 --- a/vendor.sum +++ b/vendor.sum @@ -66,8 +66,8 @@ github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 h1:EHZfspsnLAz8Hzccd67D5abwLiqoqym2jz/jOS39mCk= +github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= diff --git a/vendor/github.com/docker/go-events/README.md b/vendor/github.com/docker/go-events/README.md index 0acafc279a38..662d855fb330 100644 --- a/vendor/github.com/docker/go-events/README.md +++ b/vendor/github.com/docker/go-events/README.md @@ -1,7 +1,6 @@ # Docker Events Package [![GoDoc](https://godoc.org/github.com/docker/go-events?status.svg)](https://godoc.org/github.com/docker/go-events) -[![Circle CI](https://circleci.com/gh/docker/go-events.svg?style=shield)](https://circleci.com/gh/docker/go-events) The Docker `events` package implements a composable event distribution package for Go. diff --git a/vendor/github.com/docker/go-events/SECURITY.md b/vendor/github.com/docker/go-events/SECURITY.md new file mode 100644 index 000000000000..610eef2c9e62 --- /dev/null +++ b/vendor/github.com/docker/go-events/SECURITY.md @@ -0,0 +1,36 @@ +# Security Policy + +The maintainers of the Docker Events package take security seriously. If you discover +a security issue, please bring it to their attention right away! + +## Reporting a Vulnerability + +Please **DO NOT** file a public issue, instead send your report privately +to [security@docker.com](mailto:security@docker.com). + +Reporter(s) can expect a response within 72 hours, acknowledging the issue was +received. + +## Review Process + +After receiving the report, an initial triage and technical analysis is +performed to confirm the report and determine its scope. We may request +additional information in this stage of the process. + +Once a reviewer has confirmed the relevance of the report, a draft security +advisory will be created on GitHub. The draft advisory will be used to discuss +the issue with maintainers, the reporter(s), and where applicable, other +affected parties under embargo. + +If the vulnerability is accepted, a timeline for developing a patch, public +disclosure, and patch release will be determined. If there is an embargo period +on public disclosure before the patch release, the reporter(s) are expected to +participate in the discussion of the timeline and abide by agreed upon dates +for public disclosure. + +## Accreditation + +Security reports are greatly appreciated and we will publicly thank you, +although we will keep your name confidential if you request it. We also like to +send gifts - if you're into swag, make sure to let us know. We do not currently +offer a paid security bounty program at this time. diff --git a/vendor/github.com/docker/go-events/vendor.mod b/vendor/github.com/docker/go-events/vendor.mod new file mode 100644 index 000000000000..66a2cd9e5c6c --- /dev/null +++ b/vendor/github.com/docker/go-events/vendor.mod @@ -0,0 +1,5 @@ +module github.com/docker/go-events + +go 1.13 + +require github.com/sirupsen/logrus v1.9.3 diff --git a/vendor/github.com/docker/go-events/vendor.sum b/vendor/github.com/docker/go-events/vendor.sum new file mode 100644 index 000000000000..9243c28735dd --- /dev/null +++ b/vendor/github.com/docker/go-events/vendor.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/modules.txt b/vendor/modules.txt index bc28cd3a0a12..31dd6d398037 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -108,7 +108,7 @@ github.com/docker/go/canonical/json github.com/docker/go-connections/nat github.com/docker/go-connections/sockets github.com/docker/go-connections/tlsconfig -# github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c +# github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 ## explicit github.com/docker/go-events # github.com/docker/go-metrics v0.0.1 From a621313658ba68d9e790b0e3d2b11c66b3acac19 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sat, 23 Aug 2025 01:43:04 +0200 Subject: [PATCH 112/193] cli/command: rename vars for consistency and prevent shadowing Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 9fd71c8347f59a6845b04402ffa0d36dd6b1fa65) Signed-off-by: Sebastiaan van Stijn --- cli/command/cli_test.go | 6 +++--- cli/command/container/create_test.go | 4 ++-- cli/command/container/exec_test.go | 4 ++-- cli/command/idresolver/idresolver_test.go | 20 ++++++++--------- cli/command/network/list.go | 12 +++++------ cli/command/node/cmd.go | 2 ++ cli/command/node/demote.go | 11 +++++----- cli/command/node/inspect.go | 15 ++++++------- cli/command/node/list.go | 20 ++++++++--------- cli/command/node/promote.go | 11 +++++----- cli/command/node/ps.go | 14 ++++++------ cli/command/node/update.go | 18 +++++++--------- cli/command/secret/create.go | 10 ++++----- cli/command/secret/inspect.go | 13 ++++++------ cli/command/secret/ls.go | 12 +++++------ cli/command/service/inspect.go | 14 ++++++------ cli/command/service/opts_test.go | 4 ++-- cli/command/service/ps_test.go | 25 +++++++++++----------- cli/command/service/update_test.go | 12 +++++------ cli/command/stack/remove_test.go | 22 +++++++++---------- cli/command/stack/swarm/deploy_test.go | 6 +++--- cli/command/stack/swarm/ps.go | 10 ++++----- cli/command/swarm/ca.go | 26 +++++++++++------------ cli/command/swarm/join.go | 12 +++++------ cli/command/swarm/leave.go | 8 +++---- cli/command/swarm/unlock.go | 10 ++++----- cli/command/swarm/update.go | 14 ++++++------ cli/command/system/events.go | 6 +++--- cli/command/volume/inspect.go | 13 +++++------- cli/command/volume/list.go | 12 +++++------ 30 files changed, 180 insertions(+), 186 deletions(-) diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index ea67d403632c..ef299d84f2df 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -188,16 +188,16 @@ func TestInitializeFromClient(t *testing.T) { for _, tc := range testcases { t.Run(tc.doc, func(t *testing.T) { - apiclient := &fakeClient{ + apiClient := &fakeClient{ pingFunc: tc.pingFunc, version: defaultVersion, } - cli := &DockerCli{client: apiclient} + cli := &DockerCli{client: apiClient} err := cli.Initialize(flags.NewClientOptions()) assert.NilError(t, err) assert.DeepEqual(t, cli.ServerInfo(), tc.expectedServer) - assert.Equal(t, apiclient.negotiated, tc.negotiated) + assert.Equal(t, apiClient.negotiated, tc.negotiated) }) } } diff --git a/cli/command/container/create_test.go b/cli/command/container/create_test.go index cf68ace20f95..1b7add56375c 100644 --- a/cli/command/container/create_test.go +++ b/cli/command/container/create_test.go @@ -116,7 +116,7 @@ func TestCreateContainerImagePullPolicy(t *testing.T) { t.Run(tc.PullPolicy, func(t *testing.T) { pullCounter := 0 - client := &fakeClient{ + apiClient := &fakeClient{ createContainerFunc: func( config *container.Config, hostConfig *container.HostConfig, @@ -140,7 +140,7 @@ func TestCreateContainerImagePullPolicy(t *testing.T) { return system.Info{IndexServerAddress: "https://indexserver.example.com"}, nil }, } - fakeCLI := test.NewFakeCli(client) + fakeCLI := test.NewFakeCli(apiClient) id, err := createContainer(context.Background(), fakeCLI, config, &createOptions{ name: "name", platform: runtime.GOOS, diff --git a/cli/command/container/exec_test.go b/cli/command/container/exec_test.go index 1e2cdb2759c9..c03f333efbe8 100644 --- a/cli/command/container/exec_test.go +++ b/cli/command/container/exec_test.go @@ -234,13 +234,13 @@ func TestGetExecExitStatus(t *testing.T) { } for _, testcase := range testcases { - client := &fakeClient{ + apiClient := &fakeClient{ execInspectFunc: func(id string) (container.ExecInspect, error) { assert.Check(t, is.Equal(execID, id)) return container.ExecInspect{ExitCode: testcase.exitCode}, testcase.inspectError }, } - err := getExecExitStatus(context.Background(), client, execID) + err := getExecExitStatus(context.Background(), apiClient, execID) assert.Check(t, is.Equal(testcase.expectedError, err)) } } diff --git a/cli/command/idresolver/idresolver_test.go b/cli/command/idresolver/idresolver_test.go index dbf2f3875f84..4be7d71eb51a 100644 --- a/cli/command/idresolver/idresolver_test.go +++ b/cli/command/idresolver/idresolver_test.go @@ -12,13 +12,13 @@ import ( ) func TestResolveError(t *testing.T) { - cli := &fakeClient{ + apiClient := &fakeClient{ nodeInspectFunc: func(nodeID string) (swarm.Node, []byte, error) { return swarm.Node{}, []byte{}, errors.New("error inspecting node") }, } - idResolver := New(cli, false) + idResolver := New(apiClient, false) _, err := idResolver.Resolve(context.Background(), struct{}{}, "nodeID") assert.Error(t, err, "unsupported type") @@ -26,7 +26,7 @@ func TestResolveError(t *testing.T) { func TestResolveWithNoResolveOption(t *testing.T) { resolved := false - cli := &fakeClient{ + apiClient := &fakeClient{ nodeInspectFunc: func(nodeID string) (swarm.Node, []byte, error) { resolved = true return swarm.Node{}, []byte{}, nil @@ -37,7 +37,7 @@ func TestResolveWithNoResolveOption(t *testing.T) { }, } - idResolver := New(cli, true) + idResolver := New(apiClient, true) id, err := idResolver.Resolve(context.Background(), swarm.Node{}, "nodeID") assert.NilError(t, err) @@ -47,14 +47,14 @@ func TestResolveWithNoResolveOption(t *testing.T) { func TestResolveWithCache(t *testing.T) { inspectCounter := 0 - cli := &fakeClient{ + apiClient := &fakeClient{ nodeInspectFunc: func(nodeID string) (swarm.Node, []byte, error) { inspectCounter++ return *builders.Node(builders.NodeName("node-foo")), []byte{}, nil }, } - idResolver := New(cli, false) + idResolver := New(apiClient, false) ctx := context.Background() for i := 0; i < 2; i++ { @@ -97,10 +97,10 @@ func TestResolveNode(t *testing.T) { ctx := context.Background() for _, tc := range testCases { - cli := &fakeClient{ + apiClient := &fakeClient{ nodeInspectFunc: tc.nodeInspectFunc, } - idResolver := New(cli, false) + idResolver := New(apiClient, false) id, err := idResolver.Resolve(ctx, swarm.Node{}, tc.nodeID) assert.NilError(t, err) @@ -132,10 +132,10 @@ func TestResolveService(t *testing.T) { ctx := context.Background() for _, tc := range testCases { - cli := &fakeClient{ + apiClient := &fakeClient{ serviceInspectFunc: tc.serviceInspectFunc, } - idResolver := New(cli, false) + idResolver := New(apiClient, false) id, err := idResolver.Resolve(ctx, swarm.Service{}, tc.serviceID) assert.NilError(t, err) diff --git a/cli/command/network/list.go b/cli/command/network/list.go index aac8df9629b6..b4fd10a7dd43 100644 --- a/cli/command/network/list.go +++ b/cli/command/network/list.go @@ -45,17 +45,17 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runList(ctx context.Context, dockerCli command.Cli, options listOptions) error { - client := dockerCli.Client() - networkResources, err := client.NetworkList(ctx, network.ListOptions{Filters: options.filter.Value()}) +func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) error { + apiClient := dockerCLI.Client() + networkResources, err := apiClient.NetworkList(ctx, network.ListOptions{Filters: options.filter.Value()}) if err != nil { return err } format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().NetworksFormat) > 0 && !options.quiet { - format = dockerCli.ConfigFile().NetworksFormat + if len(dockerCLI.ConfigFile().NetworksFormat) > 0 && !options.quiet { + format = dockerCLI.ConfigFile().NetworksFormat } else { format = formatter.TableFormatKey } @@ -66,7 +66,7 @@ func runList(ctx context.Context, dockerCli command.Cli, options listOptions) er }) networksCtx := formatter.Context{ - Output: dockerCli.Out(), + Output: dockerCLI.Out(), Format: newFormat(format, options.quiet), Trunc: !options.noTrunc, } diff --git a/cli/command/node/cmd.go b/cli/command/node/cmd.go index 4536ae7f41bd..6737e05864fc 100644 --- a/cli/command/node/cmd.go +++ b/cli/command/node/cmd.go @@ -55,6 +55,8 @@ func Reference(ctx context.Context, apiClient client.APIClient, ref string) (str // If there's no node ID in /info, the node probably // isn't a manager. Call a swarm-specific endpoint to // get a more specific error message. + // + // FIXME(thaJeztah): this should not require calling a Swarm endpoint, and we could just suffice with info / ping (which has swarm status). _, err = apiClient.NodeList(ctx, swarm.NodeListOptions{}) if err != nil { return "", err diff --git a/cli/command/node/demote.go b/cli/command/node/demote.go index 5ede173cd1cc..981b45cae5f9 100644 --- a/cli/command/node/demote.go +++ b/cli/command/node/demote.go @@ -22,17 +22,16 @@ func newDemoteCommand(dockerCli command.Cli) *cobra.Command { } } -func runDemote(ctx context.Context, dockerCli command.Cli, nodes []string) error { +func runDemote(ctx context.Context, dockerCLI command.Cli, nodes []string) error { demote := func(node *swarm.Node) error { if node.Spec.Role == swarm.NodeRoleWorker { - _, _ = fmt.Fprintf(dockerCli.Out(), "Node %s is already a worker.\n", node.ID) + _, _ = fmt.Fprintf(dockerCLI.Out(), "Node %s is already a worker.\n", node.ID) return errNoRoleChange } node.Spec.Role = swarm.NodeRoleWorker return nil } - success := func(nodeID string) { - _, _ = fmt.Fprintf(dockerCli.Out(), "Manager %s demoted in the swarm.\n", nodeID) - } - return updateNodes(ctx, dockerCli, nodes, demote, success) + return updateNodes(ctx, dockerCLI.Client(), nodes, demote, func(nodeID string) { + _, _ = fmt.Fprintf(dockerCLI.Out(), "Manager %s demoted in the swarm.\n", nodeID) + }) } diff --git a/cli/command/node/inspect.go b/cli/command/node/inspect.go index 8f2519e04e34..1ef4777f4d3f 100644 --- a/cli/command/node/inspect.go +++ b/cli/command/node/inspect.go @@ -41,32 +41,31 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error { - client := dockerCli.Client() +func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error { + apiClient := dockerCLI.Client() if opts.pretty { opts.format = "pretty" } getRef := func(ref string) (any, []byte, error) { - nodeRef, err := Reference(ctx, client, ref) + nodeRef, err := Reference(ctx, apiClient, ref) if err != nil { return nil, nil, err } - node, _, err := client.NodeInspectWithRaw(ctx, nodeRef) + node, _, err := apiClient.NodeInspectWithRaw(ctx, nodeRef) return node, nil, err } - f := opts.format // check if the user is trying to apply a template to the pretty format, which // is not supported - if strings.HasPrefix(f, "pretty") && f != "pretty" { + if strings.HasPrefix(opts.format, "pretty") && opts.format != "pretty" { return errors.New("cannot supply extra formatting options to the pretty template") } nodeCtx := formatter.Context{ - Output: dockerCli.Out(), - Format: newFormat(f, false), + Output: dockerCLI.Out(), + Format: newFormat(opts.format, false), } if err := inspectFormatWrite(nodeCtx, opts.nodeIds, getRef); err != nil { diff --git a/cli/command/node/list.go b/cli/command/node/list.go index 2f9e6cad1f95..821c1b4f0f94 100644 --- a/cli/command/node/list.go +++ b/cli/command/node/list.go @@ -50,20 +50,20 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runList(ctx context.Context, dockerCli command.Cli, options listOptions) error { - client := dockerCli.Client() +func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) error { + apiClient := dockerCLI.Client() - nodes, err := client.NodeList( - ctx, - swarm.NodeListOptions{Filters: options.filter.Value()}) + nodes, err := apiClient.NodeList(ctx, swarm.NodeListOptions{ + Filters: options.filter.Value(), + }) if err != nil { return err } - info := system.Info{} + var info system.Info if len(nodes) > 0 && !options.quiet { // only non-empty nodes and not quiet, should we call /info api - info, err = client.Info(ctx) + info, err = apiClient.Info(ctx) if err != nil { return err } @@ -72,13 +72,13 @@ func runList(ctx context.Context, dockerCli command.Cli, options listOptions) er format := options.format if len(format) == 0 { format = formatter.TableFormatKey - if len(dockerCli.ConfigFile().NodesFormat) > 0 && !options.quiet { - format = dockerCli.ConfigFile().NodesFormat + if len(dockerCLI.ConfigFile().NodesFormat) > 0 && !options.quiet { + format = dockerCLI.ConfigFile().NodesFormat } } nodesCtx := formatter.Context{ - Output: dockerCli.Out(), + Output: dockerCLI.Out(), Format: newFormat(format, options.quiet), } sort.Slice(nodes, func(i, j int) bool { diff --git a/cli/command/node/promote.go b/cli/command/node/promote.go index 983229526447..0ecdeb7f1e19 100644 --- a/cli/command/node/promote.go +++ b/cli/command/node/promote.go @@ -22,17 +22,16 @@ func newPromoteCommand(dockerCli command.Cli) *cobra.Command { } } -func runPromote(ctx context.Context, dockerCli command.Cli, nodes []string) error { +func runPromote(ctx context.Context, dockerCLI command.Cli, nodes []string) error { promote := func(node *swarm.Node) error { if node.Spec.Role == swarm.NodeRoleManager { - _, _ = fmt.Fprintf(dockerCli.Out(), "Node %s is already a manager.\n", node.ID) + _, _ = fmt.Fprintf(dockerCLI.Out(), "Node %s is already a manager.\n", node.ID) return errNoRoleChange } node.Spec.Role = swarm.NodeRoleManager return nil } - success := func(nodeID string) { - _, _ = fmt.Fprintf(dockerCli.Out(), "Node %s promoted to a manager in the swarm.\n", nodeID) - } - return updateNodes(ctx, dockerCli, nodes, promote, success) + return updateNodes(ctx, dockerCLI.Client(), nodes, promote, func(nodeID string) { + _, _ = fmt.Fprintf(dockerCLI.Out(), "Node %s promoted to a manager in the swarm.\n", nodeID) + }) } diff --git a/cli/command/node/ps.go b/cli/command/node/ps.go index c34ea0e676e3..3552de76af1b 100644 --- a/cli/command/node/ps.go +++ b/cli/command/node/ps.go @@ -59,8 +59,8 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runPs(ctx context.Context, dockerCli command.Cli, options psOptions) error { - client := dockerCli.Client() +func runPs(ctx context.Context, dockerCLI command.Cli, options psOptions) error { + apiClient := dockerCLI.Client() var ( errs []string @@ -68,13 +68,13 @@ func runPs(ctx context.Context, dockerCli command.Cli, options psOptions) error ) for _, nodeID := range options.nodeIDs { - nodeRef, err := Reference(ctx, client, nodeID) + nodeRef, err := Reference(ctx, apiClient, nodeID) if err != nil { errs = append(errs, err.Error()) continue } - node, _, err := client.NodeInspectWithRaw(ctx, nodeRef) + node, _, err := apiClient.NodeInspectWithRaw(ctx, nodeRef) if err != nil { errs = append(errs, err.Error()) continue @@ -83,7 +83,7 @@ func runPs(ctx context.Context, dockerCli command.Cli, options psOptions) error filter := options.filter.Value() filter.Add("node", node.ID) - nodeTasks, err := client.TaskList(ctx, swarm.TaskListOptions{Filters: filter}) + nodeTasks, err := apiClient.TaskList(ctx, swarm.TaskListOptions{Filters: filter}) if err != nil { errs = append(errs, err.Error()) continue @@ -94,11 +94,11 @@ func runPs(ctx context.Context, dockerCli command.Cli, options psOptions) error format := options.format if len(format) == 0 { - format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet) + format = task.DefaultFormat(dockerCLI.ConfigFile(), options.quiet) } if len(errs) == 0 || len(tasks) != 0 { - if err := task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format); err != nil { + if err := task.Print(ctx, dockerCLI, tasks, idresolver.New(apiClient, options.noResolve), !options.noTrunc, options.quiet, format); err != nil { errs = append(errs, err.Error()) } } diff --git a/cli/command/node/update.go b/cli/command/node/update.go index 0c0d3bdc287a..3d4bc11401d2 100644 --- a/cli/command/node/update.go +++ b/cli/command/node/update.go @@ -9,6 +9,7 @@ import ( "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/client" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -47,18 +48,15 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runUpdate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, nodeID string) error { - success := func(_ string) { - fmt.Fprintln(dockerCli.Out(), nodeID) - } - return updateNodes(ctx, dockerCli, []string{nodeID}, mergeNodeUpdate(flags), success) +func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, nodeID string) error { + return updateNodes(ctx, dockerCLI.Client(), []string{nodeID}, mergeNodeUpdate(flags), func(_ string) { + _, _ = fmt.Fprintln(dockerCLI.Out(), nodeID) + }) } -func updateNodes(ctx context.Context, dockerCli command.Cli, nodes []string, mergeNode func(node *swarm.Node) error, success func(nodeID string)) error { - client := dockerCli.Client() - +func updateNodes(ctx context.Context, apiClient client.NodeAPIClient, nodes []string, mergeNode func(node *swarm.Node) error, success func(nodeID string)) error { for _, nodeID := range nodes { - node, _, err := client.NodeInspectWithRaw(ctx, nodeID) + node, _, err := apiClient.NodeInspectWithRaw(ctx, nodeID) if err != nil { return err } @@ -70,7 +68,7 @@ func updateNodes(ctx context.Context, dockerCli command.Cli, nodes []string, mer } return err } - err = client.NodeUpdate(ctx, node.ID, node.Version, node.Spec) + err = apiClient.NodeUpdate(ctx, node.ID, node.Version, node.Spec) if err != nil { return err } diff --git a/cli/command/secret/create.go b/cli/command/secret/create.go index 07975247010d..76f66bace750 100644 --- a/cli/command/secret/create.go +++ b/cli/command/secret/create.go @@ -49,8 +49,8 @@ func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runSecretCreate(ctx context.Context, dockerCli command.Cli, options createOptions) error { - client := dockerCli.Client() +func runSecretCreate(ctx context.Context, dockerCLI command.Cli, options createOptions) error { + apiClient := dockerCLI.Client() var secretData []byte if options.driver != "" { @@ -59,7 +59,7 @@ func runSecretCreate(ctx context.Context, dockerCli command.Cli, options createO } } else { var err error - secretData, err = readSecretData(dockerCli.In(), options.file) + secretData, err = readSecretData(dockerCLI.In(), options.file) if err != nil { return err } @@ -82,12 +82,12 @@ func runSecretCreate(ctx context.Context, dockerCli command.Cli, options createO Name: options.templateDriver, } } - r, err := client.SecretCreate(ctx, spec) + r, err := apiClient.SecretCreate(ctx, spec) if err != nil { return err } - _, _ = fmt.Fprintln(dockerCli.Out(), r.ID) + _, _ = fmt.Fprintln(dockerCLI.Out(), r.ID) return nil } diff --git a/cli/command/secret/inspect.go b/cli/command/secret/inspect.go index 7712e4be1399..fea34f2e0a3b 100644 --- a/cli/command/secret/inspect.go +++ b/cli/command/secret/inspect.go @@ -41,27 +41,26 @@ func newSecretInspectCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runSecretInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error { - client := dockerCli.Client() +func runSecretInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error { + apiClient := dockerCLI.Client() if opts.pretty { opts.format = "pretty" } getRef := func(id string) (any, []byte, error) { - return client.SecretInspectWithRaw(ctx, id) + return apiClient.SecretInspectWithRaw(ctx, id) } - f := opts.format // check if the user is trying to apply a template to the pretty format, which // is not supported - if strings.HasPrefix(f, "pretty") && f != "pretty" { + if strings.HasPrefix(opts.format, "pretty") && opts.format != "pretty" { return errors.New("cannot supply extra formatting options to the pretty template") } secretCtx := formatter.Context{ - Output: dockerCli.Out(), - Format: newFormat(f, false), + Output: dockerCLI.Out(), + Format: newFormat(opts.format, false), } if err := inspectFormatWrite(secretCtx, opts.names, getRef); err != nil { diff --git a/cli/command/secret/ls.go b/cli/command/secret/ls.go index e739d18e59aa..783ffc00c579 100644 --- a/cli/command/secret/ls.go +++ b/cli/command/secret/ls.go @@ -44,17 +44,17 @@ func newSecretListCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runSecretList(ctx context.Context, dockerCli command.Cli, options listOptions) error { - client := dockerCli.Client() +func runSecretList(ctx context.Context, dockerCLI command.Cli, options listOptions) error { + apiClient := dockerCLI.Client() - secrets, err := client.SecretList(ctx, swarm.SecretListOptions{Filters: options.filter.Value()}) + secrets, err := apiClient.SecretList(ctx, swarm.SecretListOptions{Filters: options.filter.Value()}) if err != nil { return err } format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().SecretFormat) > 0 && !options.quiet { - format = dockerCli.ConfigFile().SecretFormat + if len(dockerCLI.ConfigFile().SecretFormat) > 0 && !options.quiet { + format = dockerCLI.ConfigFile().SecretFormat } else { format = formatter.TableFormatKey } @@ -65,7 +65,7 @@ func runSecretList(ctx context.Context, dockerCli command.Cli, options listOptio }) secretCtx := formatter.Context{ - Output: dockerCli.Out(), + Output: dockerCLI.Out(), Format: newFormat(format, options.quiet), } return formatWrite(secretCtx, secrets) diff --git a/cli/command/service/inspect.go b/cli/command/service/inspect.go index 2e28507a440e..d94aeceb9c21 100644 --- a/cli/command/service/inspect.go +++ b/cli/command/service/inspect.go @@ -57,8 +57,8 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error { - client := dockerCli.Client() +func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error { + apiClient := dockerCLI.Client() if opts.pretty { opts.format = "pretty" @@ -66,7 +66,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) getRef := func(ref string) (any, []byte, error) { // Service inspect shows defaults values in empty fields. - service, _, err := client.ServiceInspectWithRaw(ctx, ref, swarm.ServiceInspectOptions{InsertDefaults: true}) + service, _, err := apiClient.ServiceInspectWithRaw(ctx, ref, swarm.ServiceInspectOptions{InsertDefaults: true}) if err == nil || !errdefs.IsNotFound(err) { return service, nil, err } @@ -74,7 +74,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) } getNetwork := func(ref string) (any, []byte, error) { - nw, _, err := client.NetworkInspectWithRaw(ctx, ref, network.InspectOptions{Scope: "swarm"}) + nw, _, err := apiClient.NetworkInspectWithRaw(ctx, ref, network.InspectOptions{Scope: "swarm"}) if err == nil || !errdefs.IsNotFound(err) { return nw, nil, err } @@ -84,8 +84,8 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) f := opts.format if len(f) == 0 { f = "raw" - if len(dockerCli.ConfigFile().ServiceInspectFormat) > 0 { - f = dockerCli.ConfigFile().ServiceInspectFormat + if len(dockerCLI.ConfigFile().ServiceInspectFormat) > 0 { + f = dockerCLI.ConfigFile().ServiceInspectFormat } } @@ -96,7 +96,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) } serviceCtx := formatter.Context{ - Output: dockerCli.Out(), + Output: dockerCLI.Out(), Format: newFormat(f), } diff --git a/cli/command/service/opts_test.go b/cli/command/service/opts_test.go index 799d32cebd2d..22ae28fa5bea 100644 --- a/cli/command/service/opts_test.go +++ b/cli/command/service/opts_test.go @@ -190,7 +190,7 @@ func TestToServiceNetwork(t *testing.T) { {Name: "zzz-network", ID: "id111"}, } - client := &fakeClient{ + apiClient := &fakeClient{ networkInspectFunc: func(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, error) { for _, nw := range nws { if nw.ID == networkID || nw.Name == networkID { @@ -212,7 +212,7 @@ func TestToServiceNetwork(t *testing.T) { ctx := context.Background() flags := newCreateCommand(nil).Flags() - service, err := o.ToService(ctx, client, flags) + service, err := o.ToService(ctx, apiClient, flags) assert.NilError(t, err) assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id111"}, {Target: "id555"}, {Target: "id999"}}, service.TaskTemplate.Networks)) } diff --git a/cli/command/service/ps_test.go b/cli/command/service/ps_test.go index 5301846a551a..46e0dafe0232 100644 --- a/cli/command/service/ps_test.go +++ b/cli/command/service/ps_test.go @@ -15,7 +15,7 @@ import ( ) func TestCreateFilter(t *testing.T) { - client := &fakeClient{ + apiClient := &fakeClient{ serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) { return []swarm.Service{ {ID: "idmatch"}, @@ -33,7 +33,7 @@ func TestCreateFilter(t *testing.T) { filter: filter, } - actual, notfound, err := createFilter(context.Background(), client, options) + actual, notfound, err := createFilter(context.Background(), apiClient, options) assert.NilError(t, err) assert.Check(t, is.DeepEqual(notfound, []string{"no such service: notfound"})) @@ -47,7 +47,7 @@ func TestCreateFilter(t *testing.T) { } func TestCreateFilterWithAmbiguousIDPrefixError(t *testing.T) { - client := &fakeClient{ + apiClient := &fakeClient{ serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) { return []swarm.Service{ {ID: "aaaone"}, @@ -59,22 +59,22 @@ func TestCreateFilterWithAmbiguousIDPrefixError(t *testing.T) { services: []string{"aaa"}, filter: opts.NewFilterOpt(), } - _, _, err := createFilter(context.Background(), client, options) + _, _, err := createFilter(context.Background(), apiClient, options) assert.Error(t, err, "multiple services found with provided prefix: aaa") } func TestCreateFilterNoneFound(t *testing.T) { - client := &fakeClient{} + apiClient := &fakeClient{} options := psOptions{ services: []string{"foo", "notfound"}, filter: opts.NewFilterOpt(), } - _, _, err := createFilter(context.Background(), client, options) + _, _, err := createFilter(context.Background(), apiClient, options) assert.Error(t, err, "no such service: foo\nno such service: notfound") } func TestRunPSWarnsOnNotFound(t *testing.T) { - client := &fakeClient{ + apiClient := &fakeClient{ serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) { return []swarm.Service{ {ID: "foo"}, @@ -82,7 +82,7 @@ func TestRunPSWarnsOnNotFound(t *testing.T) { }, } - cli := test.NewFakeCli(client) + cli := test.NewFakeCli(apiClient) options := psOptions{ services: []string{"foo", "bar"}, filter: opts.NewFilterOpt(), @@ -95,7 +95,7 @@ func TestRunPSWarnsOnNotFound(t *testing.T) { } func TestRunPSQuiet(t *testing.T) { - client := &fakeClient{ + apiClient := &fakeClient{ serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) { return []swarm.Service{{ID: "foo"}}, nil }, @@ -104,7 +104,7 @@ func TestRunPSQuiet(t *testing.T) { }, } - cli := test.NewFakeCli(client) + cli := test.NewFakeCli(apiClient) ctx := context.Background() err := runPS(ctx, cli, psOptions{services: []string{"foo"}, quiet: true, filter: opts.NewFilterOpt()}) assert.NilError(t, err) @@ -119,13 +119,14 @@ func TestUpdateNodeFilter(t *testing.T) { filters.Arg("node", "self"), ) - client := &fakeClient{ + apiClient := &fakeClient{ infoFunc: func(_ context.Context) (system.Info, error) { return system.Info{Swarm: swarm.Info{NodeID: selfNodeID}}, nil }, } - updateNodeFilter(context.Background(), client, filter) + err := updateNodeFilter(context.Background(), apiClient, filter) + assert.NilError(t, err) expected := filters.NewArgs( filters.Arg("node", "one"), diff --git a/cli/command/service/update_test.go b/cli/command/service/update_test.go index d022b0c3d507..239ba8249197 100644 --- a/cli/command/service/update_test.go +++ b/cli/command/service/update_test.go @@ -851,7 +851,7 @@ func TestUpdateNetworks(t *testing.T) { {Name: "zzz-network", ID: "id111"}, } - client := &fakeClient{ + apiClient := &fakeClient{ networkInspectFunc: func(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, error) { for _, nw := range nws { if nw.ID == networkID || nw.Name == networkID { @@ -874,28 +874,28 @@ func TestUpdateNetworks(t *testing.T) { flags := newUpdateCommand(nil).Flags() err := flags.Set(flagNetworkAdd, "aaa-network") assert.NilError(t, err) - err = updateService(ctx, client, flags, &svc) + err = updateService(ctx, apiClient, flags, &svc) assert.NilError(t, err) assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id555"}, {Target: "id999"}}, svc.TaskTemplate.Networks)) flags = newUpdateCommand(nil).Flags() err = flags.Set(flagNetworkAdd, "aaa-network") assert.NilError(t, err) - err = updateService(ctx, client, flags, &svc) + err = updateService(ctx, apiClient, flags, &svc) assert.Error(t, err, "service is already attached to network aaa-network") assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id555"}, {Target: "id999"}}, svc.TaskTemplate.Networks)) flags = newUpdateCommand(nil).Flags() err = flags.Set(flagNetworkAdd, "id555") assert.NilError(t, err) - err = updateService(ctx, client, flags, &svc) + err = updateService(ctx, apiClient, flags, &svc) assert.Error(t, err, "service is already attached to network id555") assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id555"}, {Target: "id999"}}, svc.TaskTemplate.Networks)) flags = newUpdateCommand(nil).Flags() err = flags.Set(flagNetworkRemove, "id999") assert.NilError(t, err) - err = updateService(ctx, client, flags, &svc) + err = updateService(ctx, apiClient, flags, &svc) assert.NilError(t, err) assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id555"}}, svc.TaskTemplate.Networks)) @@ -904,7 +904,7 @@ func TestUpdateNetworks(t *testing.T) { assert.NilError(t, err) err = flags.Set(flagNetworkRemove, "aaa-network") assert.NilError(t, err) - err = updateService(ctx, client, flags, &svc) + err = updateService(ctx, apiClient, flags, &svc) assert.NilError(t, err) assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id999"}}, svc.TaskTemplate.Networks)) } diff --git a/cli/command/stack/remove_test.go b/cli/command/stack/remove_test.go index 1a6923b747d7..6c843e6f6ecb 100644 --- a/cli/command/stack/remove_test.go +++ b/cli/command/stack/remove_test.go @@ -99,14 +99,14 @@ func TestRemoveStackSkipEmpty(t *testing.T) { allConfigs := []string{objectName("bar", "config1")} allConfigIDs := buildObjectIDs(allConfigs) - fakeClient := &fakeClient{ + apiClient := &fakeClient{ version: "1.30", services: allServices, networks: allNetworks, secrets: allSecrets, configs: allConfigs, } - fakeCli := test.NewFakeCli(fakeClient) + fakeCli := test.NewFakeCli(apiClient) cmd := newRemoveCommand(fakeCli) cmd.SetArgs([]string{"foo", "bar"}) @@ -120,10 +120,10 @@ func TestRemoveStackSkipEmpty(t *testing.T) { } assert.Check(t, is.Equal(strings.Join(expectedList, "\n"), fakeCli.OutBuffer().String())) assert.Check(t, is.Contains(fakeCli.ErrBuffer().String(), "Nothing found in stack: foo\n")) - assert.Check(t, is.DeepEqual(allServiceIDs, fakeClient.removedServices)) - assert.Check(t, is.DeepEqual(allNetworkIDs, fakeClient.removedNetworks)) - assert.Check(t, is.DeepEqual(allSecretIDs, fakeClient.removedSecrets)) - assert.Check(t, is.DeepEqual(allConfigIDs, fakeClient.removedConfigs)) + assert.Check(t, is.DeepEqual(allServiceIDs, apiClient.removedServices)) + assert.Check(t, is.DeepEqual(allNetworkIDs, apiClient.removedNetworks)) + assert.Check(t, is.DeepEqual(allSecretIDs, apiClient.removedSecrets)) + assert.Check(t, is.DeepEqual(allConfigIDs, apiClient.removedConfigs)) } func TestRemoveContinueAfterError(t *testing.T) { @@ -140,7 +140,7 @@ func TestRemoveContinueAfterError(t *testing.T) { allConfigIDs := buildObjectIDs(allConfigs) removedServices := []string{} - cli := &fakeClient{ + apiClient := &fakeClient{ version: "1.30", services: allServices, networks: allNetworks, @@ -156,14 +156,14 @@ func TestRemoveContinueAfterError(t *testing.T) { return nil }, } - cmd := newRemoveCommand(test.NewFakeCli(cli)) + cmd := newRemoveCommand(test.NewFakeCli(apiClient)) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) cmd.SetArgs([]string{"foo", "bar"}) assert.Error(t, cmd.Execute(), "failed to remove some resources from stack: foo") assert.Check(t, is.DeepEqual(allServiceIDs, removedServices)) - assert.Check(t, is.DeepEqual(allNetworkIDs, cli.removedNetworks)) - assert.Check(t, is.DeepEqual(allSecretIDs, cli.removedSecrets)) - assert.Check(t, is.DeepEqual(allConfigIDs, cli.removedConfigs)) + assert.Check(t, is.DeepEqual(allNetworkIDs, apiClient.removedNetworks)) + assert.Check(t, is.DeepEqual(allSecretIDs, apiClient.removedSecrets)) + assert.Check(t, is.DeepEqual(allConfigIDs, apiClient.removedConfigs)) } diff --git a/cli/command/stack/swarm/deploy_test.go b/cli/command/stack/swarm/deploy_test.go index 1c62eb3e9832..e37d5812a5a9 100644 --- a/cli/command/stack/swarm/deploy_test.go +++ b/cli/command/stack/swarm/deploy_test.go @@ -18,11 +18,11 @@ func TestPruneServices(t *testing.T) { "new": {}, "keep": {}, } - client := &fakeClient{services: []string{objectName("foo", "keep"), objectName("foo", "remove")}} - dockerCli := test.NewFakeCli(client) + apiClient := &fakeClient{services: []string{objectName("foo", "keep"), objectName("foo", "remove")}} + dockerCli := test.NewFakeCli(apiClient) pruneServices(ctx, dockerCli, namespace, services) - assert.Check(t, is.DeepEqual(buildObjectIDs([]string{objectName("foo", "remove")}), client.removedServices)) + assert.Check(t, is.DeepEqual(buildObjectIDs([]string{objectName("foo", "remove")}), apiClient.removedServices)) } // TestServiceUpdateResolveImageChanged tests that the service's diff --git a/cli/command/stack/swarm/ps.go b/cli/command/stack/swarm/ps.go index d213d525d6e7..e892fc94c975 100644 --- a/cli/command/stack/swarm/ps.go +++ b/cli/command/stack/swarm/ps.go @@ -12,11 +12,11 @@ import ( ) // RunPS is the swarm implementation of docker stack ps -func RunPS(ctx context.Context, dockerCli command.Cli, opts options.PS) error { +func RunPS(ctx context.Context, dockerCLI command.Cli, opts options.PS) error { filter := getStackFilterFromOpt(opts.Namespace, opts.Filter) - client := dockerCli.Client() - tasks, err := client.TaskList(ctx, swarm.TaskListOptions{Filters: filter}) + apiClient := dockerCLI.Client() + tasks, err := apiClient.TaskList(ctx, swarm.TaskListOptions{Filters: filter}) if err != nil { return err } @@ -27,8 +27,8 @@ func RunPS(ctx context.Context, dockerCli command.Cli, opts options.PS) error { format := opts.Format if len(format) == 0 { - format = task.DefaultFormat(dockerCli.ConfigFile(), opts.Quiet) + format = task.DefaultFormat(dockerCLI.ConfigFile(), opts.Quiet) } - return task.Print(ctx, dockerCli, tasks, idresolver.New(client, opts.NoResolve), !opts.NoTrunc, opts.Quiet, format) + return task.Print(ctx, dockerCLI, tasks, idresolver.New(apiClient, opts.NoResolve), !opts.NoTrunc, opts.Quiet, format) } diff --git a/cli/command/swarm/ca.go b/cli/command/swarm/ca.go index a648d3e83308..edde543f5a57 100644 --- a/cli/command/swarm/ca.go +++ b/cli/command/swarm/ca.go @@ -54,10 +54,10 @@ func newCACommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runCA(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, opts caOptions) error { - client := dockerCli.Client() +func runCA(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, opts caOptions) error { + apiClient := dockerCLI.Client() - swarmInspect, err := client.SwarmInspect(ctx) + swarmInspect, err := apiClient.SwarmInspect(ctx) if err != nil { return err } @@ -68,7 +68,7 @@ func runCA(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, opt return fmt.Errorf("`--%s` flag requires the `--rotate` flag to update the CA", f) } } - return displayTrustRoot(dockerCli.Out(), swarmInspect) + return displayTrustRoot(dockerCLI.Out(), swarmInspect) } if flags.Changed(flagExternalCA) && len(opts.externalCA.Value()) > 0 && !flags.Changed(flagCACert) { @@ -83,14 +83,14 @@ func runCA(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, opt } updateSwarmSpec(&swarmInspect.Spec, flags, opts) - if err := client.SwarmUpdate(ctx, swarmInspect.Version, swarmInspect.Spec, swarm.UpdateFlags{}); err != nil { + if err := apiClient.SwarmUpdate(ctx, swarmInspect.Version, swarmInspect.Spec, swarm.UpdateFlags{}); err != nil { return err } if opts.detach { return nil } - return attach(ctx, dockerCli, opts) + return attach(ctx, dockerCLI, opts) } func updateSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet, opts caOptions) { @@ -106,13 +106,13 @@ func updateSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet, opts caOptions) { } } -func attach(ctx context.Context, dockerCli command.Cli, opts caOptions) error { - client := dockerCli.Client() +func attach(ctx context.Context, dockerCLI command.Cli, opts caOptions) error { + apiClient := dockerCLI.Client() errChan := make(chan error, 1) pipeReader, pipeWriter := io.Pipe() go func() { - errChan <- progress.RootRotationProgress(ctx, client, pipeWriter) + errChan <- progress.RootRotationProgress(ctx, apiClient, pipeWriter) }() if opts.quiet { @@ -120,7 +120,7 @@ func attach(ctx context.Context, dockerCli command.Cli, opts caOptions) error { return <-errChan } - err := jsonstream.Display(ctx, pipeReader, dockerCli.Out()) + err := jsonstream.Display(ctx, pipeReader, dockerCLI.Out()) if err == nil { err = <-errChan } @@ -128,17 +128,17 @@ func attach(ctx context.Context, dockerCli command.Cli, opts caOptions) error { return err } - swarmInspect, err := client.SwarmInspect(ctx) + swarmInspect, err := apiClient.SwarmInspect(ctx) if err != nil { return err } - return displayTrustRoot(dockerCli.Out(), swarmInspect) + return displayTrustRoot(dockerCLI.Out(), swarmInspect) } func displayTrustRoot(out io.Writer, info swarm.Swarm) error { if info.ClusterInfo.TLSInfo.TrustRoot == "" { return errors.New("No CA information available") } - fmt.Fprintln(out, strings.TrimSpace(info.ClusterInfo.TLSInfo.TrustRoot)) + _, _ = fmt.Fprintln(out, strings.TrimSpace(info.ClusterInfo.TLSInfo.TrustRoot)) return nil } diff --git a/cli/command/swarm/join.go b/cli/command/swarm/join.go index 3e497e7f1f60..4da2f8edbc3f 100644 --- a/cli/command/swarm/join.go +++ b/cli/command/swarm/join.go @@ -52,8 +52,8 @@ func newJoinCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runJoin(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, opts joinOptions) error { - client := dockerCli.Client() +func runJoin(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, opts joinOptions) error { + apiClient := dockerCLI.Client() req := swarm.JoinRequest{ JoinToken: opts.token, @@ -72,20 +72,20 @@ func runJoin(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, o } } - err := client.SwarmJoin(ctx, req) + err := apiClient.SwarmJoin(ctx, req) if err != nil { return err } - info, err := client.Info(ctx) + info, err := apiClient.Info(ctx) if err != nil { return err } if info.Swarm.ControlAvailable { - fmt.Fprintln(dockerCli.Out(), "This node joined a swarm as a manager.") + _, _ = fmt.Fprintln(dockerCLI.Out(), "This node joined a swarm as a manager.") } else { - fmt.Fprintln(dockerCli.Out(), "This node joined a swarm as a worker.") + _, _ = fmt.Fprintln(dockerCLI.Out(), "This node joined a swarm as a worker.") } return nil } diff --git a/cli/command/swarm/leave.go b/cli/command/swarm/leave.go index 9d7385215825..28c8d603aa60 100644 --- a/cli/command/swarm/leave.go +++ b/cli/command/swarm/leave.go @@ -36,13 +36,13 @@ func newLeaveCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runLeave(ctx context.Context, dockerCli command.Cli, opts leaveOptions) error { - client := dockerCli.Client() +func runLeave(ctx context.Context, dockerCLI command.Cli, opts leaveOptions) error { + apiClient := dockerCLI.Client() - if err := client.SwarmLeave(ctx, opts.force); err != nil { + if err := apiClient.SwarmLeave(ctx, opts.force); err != nil { return err } - fmt.Fprintln(dockerCli.Out(), "Node left the swarm.") + _, _ = fmt.Fprintln(dockerCLI.Out(), "Node left the swarm.") return nil } diff --git a/cli/command/swarm/unlock.go b/cli/command/swarm/unlock.go index 420482ee1eda..e2af459b83b1 100644 --- a/cli/command/swarm/unlock.go +++ b/cli/command/swarm/unlock.go @@ -35,12 +35,12 @@ func newUnlockCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runUnlock(ctx context.Context, dockerCli command.Cli) error { - client := dockerCli.Client() +func runUnlock(ctx context.Context, dockerCLI command.Cli) error { + apiClient := dockerCLI.Client() // First see if the node is actually part of a swarm, and if it is actually locked first. // If it's in any other state than locked, don't ask for the key. - info, err := client.Info(ctx) + info, err := apiClient.Info(ctx) if err != nil { return err } @@ -54,12 +54,12 @@ func runUnlock(ctx context.Context, dockerCli command.Cli) error { return errors.New("Error: swarm is not locked") } - key, err := readKey(dockerCli.In(), "Enter unlock key: ") + key, err := readKey(dockerCLI.In(), "Enter unlock key: ") if err != nil { return err } - return client.SwarmUnlock(ctx, swarm.UnlockRequest{ + return apiClient.SwarmUnlock(ctx, swarm.UnlockRequest{ UnlockKey: key, }) } diff --git a/cli/command/swarm/update.go b/cli/command/swarm/update.go index 2e853a9312a0..c356e994a067 100644 --- a/cli/command/swarm/update.go +++ b/cli/command/swarm/update.go @@ -41,12 +41,12 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runUpdate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, opts swarmOptions) error { - client := dockerCli.Client() +func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, opts swarmOptions) error { + apiClient := dockerCLI.Client() var updateFlags swarm.UpdateFlags - swarmInspect, err := client.SwarmInspect(ctx) + swarmInspect, err := apiClient.SwarmInspect(ctx) if err != nil { return err } @@ -57,19 +57,19 @@ func runUpdate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, curAutoLock := swarmInspect.Spec.EncryptionConfig.AutoLockManagers - err = client.SwarmUpdate(ctx, swarmInspect.Version, swarmInspect.Spec, updateFlags) + err = apiClient.SwarmUpdate(ctx, swarmInspect.Version, swarmInspect.Spec, updateFlags) if err != nil { return err } - fmt.Fprintln(dockerCli.Out(), "Swarm updated.") + _, _ = fmt.Fprintln(dockerCLI.Out(), "Swarm updated.") if curAutoLock && !prevAutoLock { - unlockKeyResp, err := client.SwarmGetUnlockKey(ctx) + unlockKeyResp, err := apiClient.SwarmGetUnlockKey(ctx) if err != nil { return errors.Wrap(err, "could not fetch unlock key") } - printUnlockCommand(dockerCli.Out(), unlockKeyResp.UnlockKey) + printUnlockCommand(dockerCLI.Out(), unlockKeyResp.UnlockKey) } return nil diff --git a/cli/command/system/events.go b/cli/command/system/events.go index d2f8750ca0be..782f9eff5224 100644 --- a/cli/command/system/events.go +++ b/cli/command/system/events.go @@ -62,7 +62,7 @@ func newEventsCommand(dockerCLI command.Cli) *cobra.Command { return cmd } -func runEvents(ctx context.Context, dockerCli command.Cli, options *eventsOptions) error { +func runEvents(ctx context.Context, dockerCLI command.Cli, options *eventsOptions) error { tmpl, err := makeTemplate(options.format) if err != nil { return cli.StatusError{ @@ -71,14 +71,14 @@ func runEvents(ctx context.Context, dockerCli command.Cli, options *eventsOption } } ctx, cancel := context.WithCancel(ctx) - evts, errs := dockerCli.Client().Events(ctx, events.ListOptions{ + evts, errs := dockerCLI.Client().Events(ctx, events.ListOptions{ Since: options.since, Until: options.until, Filters: options.filter.Value(), }) defer cancel() - out := dockerCli.Out() + out := dockerCLI.Out() for { select { diff --git a/cli/command/volume/inspect.go b/cli/command/volume/inspect.go index 1105a052068b..cc3abb72e8c3 100644 --- a/cli/command/volume/inspect.go +++ b/cli/command/volume/inspect.go @@ -38,13 +38,10 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error { - client := dockerCli.Client() - - getVolFunc := func(name string) (any, []byte, error) { - i, err := client.VolumeInspect(ctx, name) +func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error { + apiClient := dockerCLI.Client() + return inspect.Inspect(dockerCLI.Out(), opts.names, opts.format, func(name string) (any, []byte, error) { + i, err := apiClient.VolumeInspect(ctx, name) return i, nil, err - } - - return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getVolFunc) + }) } diff --git a/cli/command/volume/list.go b/cli/command/volume/list.go index e51bfe5e1c6e..297c90b7c987 100644 --- a/cli/command/volume/list.go +++ b/cli/command/volume/list.go @@ -51,17 +51,17 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runList(ctx context.Context, dockerCli command.Cli, options listOptions) error { - client := dockerCli.Client() - volumes, err := client.VolumeList(ctx, volume.ListOptions{Filters: options.filter.Value()}) +func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) error { + apiClient := dockerCLI.Client() + volumes, err := apiClient.VolumeList(ctx, volume.ListOptions{Filters: options.filter.Value()}) if err != nil { return err } format := options.format if len(format) == 0 && !options.cluster { - if len(dockerCli.ConfigFile().VolumesFormat) > 0 && !options.quiet { - format = dockerCli.ConfigFile().VolumesFormat + if len(dockerCLI.ConfigFile().VolumesFormat) > 0 && !options.quiet { + format = dockerCLI.ConfigFile().VolumesFormat } else { format = formatter.TableFormatKey } @@ -90,7 +90,7 @@ func runList(ctx context.Context, dockerCli command.Cli, options listOptions) er }) volumeCtx := formatter.Context{ - Output: dockerCli.Out(), + Output: dockerCLI.Out(), Format: formatter.NewVolumeFormat(format, options.quiet), } return formatter.VolumeWrite(volumeCtx, volumes.Volumes) From 8c6a7fcbc9e4ff54ebaebcc5eada3a5df40f20c3 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 28 Aug 2025 10:13:45 +0200 Subject: [PATCH 113/193] deprecate cli/command/stack/formatter Functions and types in this package were exported as part of the "compose on kubernetes" feature, which was deprecated and removed. These functions are meant for internal use, and will be removed in the next release. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 30774ed1f261493fa94bbdebc448807d4fdd70b6) Signed-off-by: Sebastiaan van Stijn --- .golangci.yml | 8 ++++++++ cli/command/stack/formatter/formatter.go | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index bf093a63c7ac..1365f9fa3f2b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -222,6 +222,14 @@ linters: linters: - staticcheck + # Ignore deprecation linting for cli/command/stack/*. + # + # FIXME(thaJeztah): remove exception once these functions are un-exported or internal; see https://github.com/docker/cli/pull/6389 + - text: '^(SA1019): ' + path: "cli/command/stack" + linters: + - staticcheck + # Log a warning if an exclusion rule is unused. # Default: false warn-unused: true diff --git a/cli/command/stack/formatter/formatter.go b/cli/command/stack/formatter/formatter.go index 07b322f0616a..7cfe8a89af0f 100644 --- a/cli/command/stack/formatter/formatter.go +++ b/cli/command/stack/formatter/formatter.go @@ -8,21 +8,31 @@ import ( const ( // SwarmStackTableFormat is the default Swarm stack format + // + // Deprecated: this type was for internal use and will be removed in the next release. SwarmStackTableFormat formatter.Format = "table {{.Name}}\t{{.Services}}" stackServicesHeader = "SERVICES" // TableFormatKey is an alias for formatter.TableFormatKey + // + // Deprecated: this type was for internal use and will be removed in the next release. TableFormatKey = formatter.TableFormatKey ) // Context is an alias for formatter.Context +// +// Deprecated: this type was for internal use and will be removed in the next release. type Context = formatter.Context // Format is an alias for formatter.Format +// +// Deprecated: this type was for internal use and will be removed in the next release. type Format = formatter.Format // Stack contains deployed stack information. +// +// Deprecated: this type was for internal use and will be removed in the next release. type Stack struct { // Name is the name of the stack Name string @@ -31,6 +41,8 @@ type Stack struct { } // StackWrite writes formatted stacks using the Context +// +// Deprecated: this function was for internal use and will be removed in the next release. func StackWrite(ctx formatter.Context, stacks []*Stack) error { render := func(format func(subContext formatter.SubContext) error) error { for _, stack := range stacks { From 7c6f58affe2b4e45c4ae8c01f56c3772dd20a6f9 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 28 Aug 2025 10:14:50 +0200 Subject: [PATCH 114/193] deprecate cli/command/stack/loader Functions and types in this package were exported as part of the "compose on kubernetes" feature, which was deprecated and removed. These functions are meant for internal use, and will be removed in the next release. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit ad6ab189a642625d54053ac6e7080e8940288d2d) Signed-off-by: Sebastiaan van Stijn --- cli/command/stack/loader/loader.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cli/command/stack/loader/loader.go b/cli/command/stack/loader/loader.go index 75d485b01464..8187efb5a669 100644 --- a/cli/command/stack/loader/loader.go +++ b/cli/command/stack/loader/loader.go @@ -22,6 +22,8 @@ import ( ) // LoadComposefile parse the composefile specified in the cli and returns its Config and version. +// +// Deprecated: this function was for internal use and will be removed in the next release. func LoadComposefile(dockerCli command.Cli, opts options.Deploy) (*composetypes.Config, error) { configDetails, err := GetConfigDetails(opts.Composefiles, dockerCli.In()) if err != nil { @@ -84,6 +86,8 @@ func propertyWarnings(properties map[string]string) string { } // GetConfigDetails parse the composefiles specified in the cli and returns their ConfigDetails +// +// Deprecated: this function was for internal use and will be removed in the next release. func GetConfigDetails(composefiles []string, stdin io.Reader) (composetypes.ConfigDetails, error) { var details composetypes.ConfigDetails From 91de2a6daee0ddeaecab41097fa286fee1aa278e Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 28 Aug 2025 10:15:13 +0200 Subject: [PATCH 115/193] deprecate cli/command/stack/options Functions and types in this package were exported as part of the "compose on kubernetes" feature, which was deprecated and removed. These functions are meant for internal use, and will be removed in the next release. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit f0e5a0d6545399477660087e2db69ebbf831666d) Signed-off-by: Sebastiaan van Stijn --- cli/command/stack/options/opts.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cli/command/stack/options/opts.go b/cli/command/stack/options/opts.go index 28d4c3262207..bd239e892ffd 100644 --- a/cli/command/stack/options/opts.go +++ b/cli/command/stack/options/opts.go @@ -3,6 +3,8 @@ package options import "github.com/docker/cli/opts" // Deploy holds docker stack deploy options +// +// Deprecated: this type was for internal use and will be removed in the next release. type Deploy struct { Composefiles []string Namespace string @@ -14,18 +16,24 @@ type Deploy struct { } // Config holds docker stack config options +// +// Deprecated: this type was for internal use and will be removed in the next release. type Config struct { Composefiles []string SkipInterpolation bool } // List holds docker stack ls options +// +// Deprecated: this type was for internal use and will be removed in the next release. type List struct { Format string AllNamespaces bool } // PS holds docker stack ps options +// +// Deprecated: this type was for internal use and will be removed in the next release. type PS struct { Filter opts.FilterOpt NoTrunc bool @@ -36,12 +44,16 @@ type PS struct { } // Remove holds docker stack remove options +// +// Deprecated: this type was for internal use and will be removed in the next release. type Remove struct { Namespaces []string Detach bool } // Services holds docker stack services options +// +// Deprecated: this type was for internal use and will be removed in the next release. type Services struct { Quiet bool Format string From 19b7dfa1cf44104836af4ab7988087ab6ef59530 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 28 Aug 2025 10:15:33 +0200 Subject: [PATCH 116/193] deprecate cli/command/stack/swarm Functions and types in this package were exported as part of the "compose on kubernetes" feature, which was deprecated and removed. These functions are meant for internal use, and will be removed in the next release. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 036d3a6bab54fdffd9804ab2367fb9a14e62893b) Signed-off-by: Sebastiaan van Stijn --- cli/command/stack/swarm/deploy.go | 2 ++ cli/command/stack/swarm/list.go | 2 ++ cli/command/stack/swarm/ps.go | 2 ++ cli/command/stack/swarm/remove.go | 2 ++ cli/command/stack/swarm/services.go | 12 +++++++----- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cli/command/stack/swarm/deploy.go b/cli/command/stack/swarm/deploy.go index bfd71f5f6566..cf3a52418ff3 100644 --- a/cli/command/stack/swarm/deploy.go +++ b/cli/command/stack/swarm/deploy.go @@ -23,6 +23,8 @@ const ( ) // RunDeploy is the swarm implementation of docker stack deploy +// +// Deprecated: this function was for internal use and will be removed in the next release. func RunDeploy(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, opts *options.Deploy, cfg *composetypes.Config) error { if err := validateResolveImageFlag(opts); err != nil { return err diff --git a/cli/command/stack/swarm/list.go b/cli/command/stack/swarm/list.go index ec951d71d395..22b175a1eda0 100644 --- a/cli/command/stack/swarm/list.go +++ b/cli/command/stack/swarm/list.go @@ -11,6 +11,8 @@ import ( ) // GetStacks lists the swarm stacks. +// +// Deprecated: this function was for internal use and will be removed in the next release. func GetStacks(ctx context.Context, apiClient client.ServiceAPIClient) ([]*formatter.Stack, error) { services, err := apiClient.ServiceList( ctx, diff --git a/cli/command/stack/swarm/ps.go b/cli/command/stack/swarm/ps.go index e892fc94c975..fc16b7eb725d 100644 --- a/cli/command/stack/swarm/ps.go +++ b/cli/command/stack/swarm/ps.go @@ -12,6 +12,8 @@ import ( ) // RunPS is the swarm implementation of docker stack ps +// +// Deprecated: this function was for internal use and will be removed in the next release. func RunPS(ctx context.Context, dockerCLI command.Cli, opts options.PS) error { filter := getStackFilterFromOpt(opts.Namespace, opts.Filter) diff --git a/cli/command/stack/swarm/remove.go b/cli/command/stack/swarm/remove.go index cd426d5111d5..42812262ab95 100644 --- a/cli/command/stack/swarm/remove.go +++ b/cli/command/stack/swarm/remove.go @@ -15,6 +15,8 @@ import ( ) // RunRemove is the swarm implementation of docker stack remove +// +// Deprecated: this function was for internal use and will be removed in the next release. func RunRemove(ctx context.Context, dockerCli command.Cli, opts options.Remove) error { apiClient := dockerCli.Client() diff --git a/cli/command/stack/swarm/services.go b/cli/command/stack/swarm/services.go index e75d3ccf8c80..4a2eed34f5c1 100644 --- a/cli/command/stack/swarm/services.go +++ b/cli/command/stack/swarm/services.go @@ -10,10 +10,12 @@ import ( ) // GetServices is the swarm implementation of listing stack services -func GetServices(ctx context.Context, dockerCli command.Cli, opts options.Services) ([]swarm.Service, error) { +// +// Deprecated: this function was for internal use and will be removed in the next release. +func GetServices(ctx context.Context, dockerCLI command.Cli, opts options.Services) ([]swarm.Service, error) { var ( - err error - client = dockerCli.Client() + err error + apiClient = dockerCLI.Client() ) listOpts := swarm.ServiceListOptions{ @@ -25,7 +27,7 @@ func GetServices(ctx context.Context, dockerCli command.Cli, opts options.Servic Status: !opts.Quiet, } - services, err := client.ServiceList(ctx, listOpts) + services, err := apiClient.ServiceList(ctx, listOpts) if err != nil { return nil, err } @@ -43,7 +45,7 @@ func GetServices(ctx context.Context, dockerCli command.Cli, opts options.Servic // 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, client, services) + services, err = service.AppendServiceStatus(ctx, apiClient, services) if err != nil { return nil, err } From e636ed2ae5645ee9b3d6b00f707d6617ed3d5632 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 28 Aug 2025 10:16:39 +0200 Subject: [PATCH 117/193] cli/command/stack: deprecate RunList, RunServices Functions and types in this package were exported as part of the "compose on kubernetes" feature, which was deprecated and removed. These functions are meant for internal use, and will be removed in the next release. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit d16c56066427be5e988c197d40c93475446bcdda) Signed-off-by: Sebastiaan van Stijn --- cli/command/stack/list.go | 21 +++++++++++++++------ cli/command/stack/services.go | 26 ++++++++++++++++++-------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index 4ec1b30085d4..1b4831d43a9e 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -16,8 +16,10 @@ import ( "github.com/spf13/cobra" ) +type listOptions = options.List + func newListCommand(dockerCli command.Cli) *cobra.Command { - opts := options.List{} + opts := listOptions{} cmd := &cobra.Command{ Use: "ls [OPTIONS]", @@ -25,7 +27,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { Short: "List stacks", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return RunList(cmd.Context(), dockerCli, opts) + return runList(cmd.Context(), dockerCli, opts) }, ValidArgsFunction: completion.NoComplete, } @@ -36,17 +38,24 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { } // RunList performs a stack list against the specified swarm cluster -func RunList(ctx context.Context, dockerCli command.Cli, opts options.List) error { - ss, err := swarm.GetStacks(ctx, dockerCli.Client()) +// +// Deprecated: this function was for internal use and will be removed in the next release. +func RunList(ctx context.Context, dockerCLI command.Cli, opts options.List) error { + return runList(ctx, dockerCLI, opts) +} + +// runList performs a stack list against the specified swarm cluster +func runList(ctx context.Context, dockerCLI command.Cli, opts listOptions) error { + ss, err := swarm.GetStacks(ctx, dockerCLI.Client()) if err != nil { return err } stacks := make([]*formatter.Stack, 0, len(ss)) stacks = append(stacks, ss...) - return format(dockerCli.Out(), opts, stacks) + return format(dockerCLI.Out(), opts, stacks) } -func format(out io.Writer, opts options.List, stacks []*formatter.Stack) error { +func format(out io.Writer, opts listOptions, stacks []*formatter.Stack) error { fmt := formatter.Format(opts.Format) if fmt == "" || fmt == formatter.TableFormatKey { fmt = formatter.SwarmStackTableFormat diff --git a/cli/command/stack/services.go b/cli/command/stack/services.go index f91ba71edfa5..cdeac2a2b9de 100644 --- a/cli/command/stack/services.go +++ b/cli/command/stack/services.go @@ -18,8 +18,11 @@ import ( "github.com/spf13/cobra" ) -func newServicesCommand(dockerCli command.Cli) *cobra.Command { - opts := options.Services{Filter: cliopts.NewFilterOpt()} +// servicesOptions holds docker stack services options +type servicesOptions = options.Services + +func newServicesCommand(dockerCLI command.Cli) *cobra.Command { + opts := servicesOptions{Filter: cliopts.NewFilterOpt()} cmd := &cobra.Command{ Use: "services [OPTIONS] STACK", @@ -30,10 +33,10 @@ func newServicesCommand(dockerCli command.Cli) *cobra.Command { if err := validateStackName(opts.Namespace); err != nil { return err } - return RunServices(cmd.Context(), dockerCli, opts) + return runServices(cmd.Context(), dockerCLI, opts) }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return completeNames(dockerCli)(cmd, args, toComplete) + return completeNames(dockerCLI)(cmd, args, toComplete) }, } flags := cmd.Flags() @@ -44,15 +47,22 @@ func newServicesCommand(dockerCli command.Cli) *cobra.Command { } // RunServices performs a stack services against the specified swarm cluster -func RunServices(ctx context.Context, dockerCli command.Cli, opts options.Services) error { - services, err := swarm.GetServices(ctx, dockerCli, opts) +// +// Deprecated: this function was for internal use and will be removed in the next release. +func RunServices(ctx context.Context, dockerCLI command.Cli, opts options.Services) error { + return runServices(ctx, dockerCLI, opts) +} + +// runServices performs a stack services against the specified swarm cluster +func runServices(ctx context.Context, dockerCLI command.Cli, opts servicesOptions) error { + services, err := swarm.GetServices(ctx, dockerCLI, opts) if err != nil { return err } - return formatWrite(dockerCli, services, opts) + return formatWrite(dockerCLI, services, opts) } -func formatWrite(dockerCLI command.Cli, services []swarmtypes.Service, opts options.Services) error { +func formatWrite(dockerCLI command.Cli, services []swarmtypes.Service, opts servicesOptions) error { // if no services in the stack, print message and exit 0 if len(services) == 0 { _, _ = fmt.Fprintln(dockerCLI.Err(), "Nothing found in stack:", opts.Namespace) From bda15c766a126396ba485ec326e4d9ddba266c1a Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 15 Apr 2025 22:11:44 +0200 Subject: [PATCH 118/193] cli/config/types: update deprecation comment for AuthConfig.Email Relates to [cli@27b2797], which forked this type from the Moby API, and [moby@6cfff7e], which made the same change on the API side. The Email field was originally used to create a new Docker Hub account through the `docker login` command. The `docker login` command could be used both to log in to an existing account (providing only username and password), or to create a new account (providing desired username and password, and an e-mail address to use for the new account). This functionality was confusing, because it was implemented when Docker Hub was the only registry, but the same functionality could not be used for other registries. This functionality was removed in Docker 1.11 (API version 1.23) through [moby@aee260d], which also removed the Email field ([engine-api@9a9e468]) as it was no longer used. However, this caused issues when using a new CLI connecting with an old daemon, as the field would no longer be serialized, and the deprecation may not yet be picked up by custom registries, so [engine-api@167efc7] added the field back, deprecated it, and added an "omitempty". There was no official "deprecated" format yet at the time, so let's make sure the deprecation follows the proper format to make sure it gets noticed. [cli@27b2797]: https://github.com/docker/cli/commit/27b2797f7deb3ca5b7f80371d825113deb1faca1 [moby@6cfff7e]: https://github.com/moby/moby/commit/6cfff7e8803a71b4acb74768b5121e7d17a9e098 [moby@aee260d]: https://github.com/moby/moby/commit/aee260d4eb3aa0fc86ee5038010b7bbc24512ae5 [engine-api@9a9e468]: https://github.com/docker-archive-public/docker.engine-api/commit/9a9e468f503eb731d6fdc9d7f98c122e1b397c86 [engine-api@167efc7]: https://github.com/docker-archive-public/docker.engine-api/commit/167efc72bb24d7ad2bcc91760a9a5d37572e104f Signed-off-by: Sebastiaan van Stijn (cherry picked from commit aab947de8f5cd2db3dd9d8ead0f38d3246557750) Signed-off-by: Sebastiaan van Stijn --- cli/config/credentials/file_store_test.go | 29 +++++----- cli/config/credentials/native_store_test.go | 63 +++++---------------- cli/config/types/authconfig.go | 4 +- 3 files changed, 30 insertions(+), 66 deletions(-) diff --git a/cli/config/credentials/file_store_test.go b/cli/config/credentials/file_store_test.go index e4a43e11fb14..d4c8375ea25e 100644 --- a/cli/config/credentials/file_store_test.go +++ b/cli/config/credentials/file_store_test.go @@ -43,13 +43,13 @@ func TestFileStoreIdempotent(t *testing.T) { }, }) authOne := types.AuthConfig{ + Username: "foo@example.com", Auth: "super_secret_token", - Email: "foo@example.com", ServerAddress: "https://example.com", } authTwo := types.AuthConfig{ + Username: "bar@example.com", Auth: "also_super_secret_token", - Email: "bar@example.com", ServerAddress: "https://other.example.com", } @@ -106,8 +106,8 @@ func TestFileStoreAddCredentials(t *testing.T) { s := NewFileStore(f) auth := types.AuthConfig{ + Username: "foo@example.com", Auth: "super_secret_token", - Email: "foo@example.com", ServerAddress: "https://example.com", } err := s.Store(auth) @@ -122,8 +122,8 @@ func TestFileStoreAddCredentials(t *testing.T) { func TestFileStoreGet(t *testing.T) { f := &fakeStore{configs: map[string]types.AuthConfig{ "https://example.com": { + Username: "foo@example.com", Auth: "super_secret_token", - Email: "foo@example.com", ServerAddress: "https://example.com", }, }} @@ -136,8 +136,8 @@ func TestFileStoreGet(t *testing.T) { if a.Auth != "super_secret_token" { t.Fatalf("expected auth `super_secret_token`, got %s", a.Auth) } - if a.Email != "foo@example.com" { - t.Fatalf("expected email `foo@example.com`, got %s", a.Email) + if a.Username != "foo@example.com" { + t.Fatalf("expected username `foo@example.com`, got %s", a.Username) } } @@ -146,13 +146,13 @@ func TestFileStoreGetAll(t *testing.T) { s2 := "https://example2.example.com" f := &fakeStore{configs: map[string]types.AuthConfig{ s1: { + Username: "foo@example.com", Auth: "super_secret_token", - Email: "foo@example.com", ServerAddress: "https://example.com", }, s2: { + Username: "foo@example2.com", Auth: "super_secret_token2", - Email: "foo@example2.com", ServerAddress: "https://example2.example.com", }, }} @@ -168,22 +168,22 @@ func TestFileStoreGetAll(t *testing.T) { if as[s1].Auth != "super_secret_token" { t.Fatalf("expected auth `super_secret_token`, got %s", as[s1].Auth) } - if as[s1].Email != "foo@example.com" { - t.Fatalf("expected email `foo@example.com`, got %s", as[s1].Email) + if as[s1].Username != "foo@example.com" { + t.Fatalf("expected username `foo@example.com`, got %s", as[s1].Username) } if as[s2].Auth != "super_secret_token2" { t.Fatalf("expected auth `super_secret_token2`, got %s", as[s2].Auth) } - if as[s2].Email != "foo@example2.com" { - t.Fatalf("expected email `foo@example2.com`, got %s", as[s2].Email) + if as[s2].Username != "foo@example2.com" { + t.Fatalf("expected username `foo@example2.com`, got %s", as[s2].Username) } } func TestFileStoreErase(t *testing.T) { f := &fakeStore{configs: map[string]types.AuthConfig{ "https://example.com": { + Username: "foo@example.com", Auth: "super_secret_token", - Email: "foo@example.com", ServerAddress: "https://example.com", }, }} @@ -203,9 +203,6 @@ func TestFileStoreErase(t *testing.T) { if a.Auth != "" { t.Fatalf("expected empty auth token, got %s", a.Auth) } - if a.Email != "" { - t.Fatalf("expected empty email, got %s", a.Email) - } } func TestConvertToHostname(t *testing.T) { diff --git a/cli/config/credentials/native_store_test.go b/cli/config/credentials/native_store_test.go index c2f843bfbb95..cb31071e602a 100644 --- a/cli/config/credentials/native_store_test.go +++ b/cli/config/credentials/native_store_test.go @@ -99,7 +99,6 @@ func TestNativeStoreAddCredentials(t *testing.T) { auth := types.AuthConfig{ Username: "foo", Password: "bar", - Email: "foo@example.com", ServerAddress: validServerAddress, } err := s.Store(auth) @@ -109,7 +108,6 @@ func TestNativeStoreAddCredentials(t *testing.T) { actual, ok := f.GetAuthConfigs()[validServerAddress] assert.Check(t, ok) expected := types.AuthConfig{ - Email: auth.Email, ServerAddress: auth.ServerAddress, } assert.Check(t, is.DeepEqual(expected, actual)) @@ -124,7 +122,6 @@ func TestNativeStoreAddInvalidCredentials(t *testing.T) { err := s.Store(types.AuthConfig{ Username: "foo", Password: "bar", - Email: "foo@example.com", ServerAddress: invalidServerAddress, }) assert.ErrorContains(t, err, "program failed") @@ -134,7 +131,7 @@ func TestNativeStoreAddInvalidCredentials(t *testing.T) { func TestNativeStoreGet(t *testing.T) { f := &fakeStore{configs: map[string]types.AuthConfig{ validServerAddress: { - Email: "foo@example.com", + Username: "foo@example.com", }, }} s := &nativeStore{ @@ -147,7 +144,6 @@ func TestNativeStoreGet(t *testing.T) { expected := types.AuthConfig{ Username: "foo", Password: "bar", - Email: "foo@example.com", ServerAddress: validServerAddress, } assert.Check(t, is.DeepEqual(expected, actual)) @@ -155,9 +151,7 @@ func TestNativeStoreGet(t *testing.T) { func TestNativeStoreGetIdentityToken(t *testing.T) { f := &fakeStore{configs: map[string]types.AuthConfig{ - validServerAddress2: { - Email: "foo@example2.com", - }, + validServerAddress2: {}, }} s := &nativeStore{ @@ -169,7 +163,6 @@ func TestNativeStoreGetIdentityToken(t *testing.T) { expected := types.AuthConfig{ IdentityToken: "abcd1234", - Email: "foo@example2.com", ServerAddress: validServerAddress2, } assert.Check(t, is.DeepEqual(expected, actual)) @@ -177,9 +170,7 @@ func TestNativeStoreGetIdentityToken(t *testing.T) { func TestNativeStoreGetAll(t *testing.T) { f := &fakeStore{configs: map[string]types.AuthConfig{ - validServerAddress: { - Email: "foo@example.com", - }, + validServerAddress: {}, }} s := &nativeStore{ @@ -189,38 +180,20 @@ func TestNativeStoreGetAll(t *testing.T) { as, err := s.GetAll() assert.NilError(t, err) assert.Check(t, is.Len(as, 2)) - - if as[validServerAddress].Username != "foo" { - t.Fatalf("expected username `foo` for %s, got %s", validServerAddress, as[validServerAddress].Username) - } - if as[validServerAddress].Password != "bar" { - t.Fatalf("expected password `bar` for %s, got %s", validServerAddress, as[validServerAddress].Password) - } - if as[validServerAddress].IdentityToken != "" { - t.Fatalf("expected identity to be empty for %s, got %s", validServerAddress, as[validServerAddress].IdentityToken) - } - if as[validServerAddress].Email != "foo@example.com" { - t.Fatalf("expected email `foo@example.com` for %s, got %s", validServerAddress, as[validServerAddress].Email) - } - if as[validServerAddress2].Username != "" { - t.Fatalf("expected username to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Username) - } - if as[validServerAddress2].Password != "" { - t.Fatalf("expected password to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Password) - } - if as[validServerAddress2].IdentityToken != "abcd1234" { - t.Fatalf("expected identity token `abcd1324` for %s, got %s", validServerAddress2, as[validServerAddress2].IdentityToken) - } - if as[validServerAddress2].Email != "" { - t.Fatalf("expected no email for %s, got %s", validServerAddress2, as[validServerAddress2].Email) + expected := types.AuthConfig{ + Username: "foo", + Password: "bar", + ServerAddress: "https://index.docker.io/v1", + IdentityToken: "", } + actual, ok := as[validServerAddress] + assert.Check(t, ok) + assert.Check(t, is.DeepEqual(expected, actual)) } func TestNativeStoreGetMissingCredentials(t *testing.T) { f := &fakeStore{configs: map[string]types.AuthConfig{ - validServerAddress: { - Email: "foo@example.com", - }, + validServerAddress: {}, }} s := &nativeStore{ @@ -233,9 +206,7 @@ func TestNativeStoreGetMissingCredentials(t *testing.T) { func TestNativeStoreGetInvalidAddress(t *testing.T) { f := &fakeStore{configs: map[string]types.AuthConfig{ - validServerAddress: { - Email: "foo@example.com", - }, + validServerAddress: {}, }} s := &nativeStore{ @@ -248,9 +219,7 @@ func TestNativeStoreGetInvalidAddress(t *testing.T) { func TestNativeStoreErase(t *testing.T) { f := &fakeStore{configs: map[string]types.AuthConfig{ - validServerAddress: { - Email: "foo@example.com", - }, + validServerAddress: {}, }} s := &nativeStore{ @@ -264,9 +233,7 @@ func TestNativeStoreErase(t *testing.T) { func TestNativeStoreEraseInvalidAddress(t *testing.T) { f := &fakeStore{configs: map[string]types.AuthConfig{ - validServerAddress: { - Email: "foo@example.com", - }, + validServerAddress: {}, }} s := &nativeStore{ diff --git a/cli/config/types/authconfig.go b/cli/config/types/authconfig.go index 056af6b84259..95eb27c868bf 100644 --- a/cli/config/types/authconfig.go +++ b/cli/config/types/authconfig.go @@ -7,8 +7,8 @@ type AuthConfig struct { Auth string `json:"auth,omitempty"` // Email is an optional value associated with the username. - // This field is deprecated and will be removed in a later - // version of docker. + // + // Deprecated: This field is deprecated since docker 1.11 (API v1.23) and will be removed in the next release. Email string `json:"email,omitempty"` ServerAddress string `json:"serveraddress,omitempty"` From c2117f956f6efdb9b3dd5056d3486d6bfce80f6d Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 14 Aug 2025 23:26:44 +0200 Subject: [PATCH 119/193] vendor: github.com/docker/docker 02b4a1a3decc (v28.4.0-dev) full diff: https://github.com/moby/moby/compare/v28.3.3...02b4a1a3decc99de2965421cea5634c1e5297266 Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 2 +- vendor.sum | 4 +- .../github.com/docker/docker/api/swagger.yaml | 5 + .../docker/api/types/build/disk_usage.go | 2 + .../docker/api/types/container/disk_usage.go | 2 + .../api/types/filters/filters_deprecated.go | 61 ++ .../docker/docker/api/types/filters/parse.go | 34 - .../docker/api/types/image/disk_usage.go | 2 + .../docker/api/types/network/endpoint.go | 6 +- .../docker/docker/api/types/network/ipam.go | 45 +- .../docker/api/types/registry/authconfig.go | 4 +- .../docker/docker/api/types/swarm/runtime.go | 10 + .../docker/api/types/swarm/runtime/gen.go | 3 - .../api/types/swarm/runtime/plugin.pb.go | 808 ------------------ .../api/types/swarm/runtime/plugin.proto | 19 - .../docker/api/types/swarm/runtime/runtime.go | 27 + .../docker/docker/api/types/swarm/task.go | 3 +- .../docker/api/types/system/disk_usage.go | 17 - .../docker/api/types/volume/disk_usage.go | 2 + .../github.com/docker/docker/client/client.go | 4 +- .../docker/docker/client/client_unix.go | 11 + .../docker/docker/client/client_windows.go | 12 + .../docker/docker/client/container_stats.go | 4 +- .../docker/docker/client/image_build.go | 2 +- .../github.com/docker/docker/client/utils.go | 13 - .../docker/internal/lazyregexp/lazyregexp.go | 90 -- .../docker/internal/multierror/multierror.go | 46 - .../docker/pkg/jsonmessage/jsonmessage.go | 6 +- vendor/modules.txt | 4 +- 29 files changed, 194 insertions(+), 1054 deletions(-) create mode 100644 vendor/github.com/docker/docker/api/types/filters/filters_deprecated.go delete mode 100644 vendor/github.com/docker/docker/api/types/swarm/runtime/gen.go delete mode 100644 vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.pb.go delete mode 100644 vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.proto create mode 100644 vendor/github.com/docker/docker/api/types/swarm/runtime/runtime.go delete mode 100644 vendor/github.com/docker/docker/api/types/system/disk_usage.go delete mode 100644 vendor/github.com/docker/docker/internal/lazyregexp/lazyregexp.go delete mode 100644 vendor/github.com/docker/docker/internal/multierror/multierror.go diff --git a/vendor.mod b/vendor.mod index b6e4c5809cac..3a6bf2ef0bba 100644 --- a/vendor.mod +++ b/vendor.mod @@ -16,7 +16,7 @@ require ( github.com/distribution/reference v0.6.0 github.com/docker/cli-docs-tool v0.10.0 github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.3.3+incompatible + github.com/docker/docker v28.3.4-0.20250828134822-02b4a1a3decc+incompatible // 28.x branch (v28.4.0-dev) github.com/docker/docker-credential-helpers v0.9.3 github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 diff --git a/vendor.sum b/vendor.sum index d447b15623ec..7ab4efcdc5f6 100644 --- a/vendor.sum +++ b/vendor.sum @@ -57,8 +57,8 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= -github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.3.4-0.20250828134822-02b4a1a3decc+incompatible h1:a4+xSvQFFYl8eRsVWyL3lwz1JubDEO77m1ovj3lFiXo= +github.com/docker/docker v28.3.4-0.20250828134822-02b4a1a3decc+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= diff --git a/vendor/github.com/docker/docker/api/swagger.yaml b/vendor/github.com/docker/docker/api/swagger.yaml index 3880635db128..16da2ce49195 100644 --- a/vendor/github.com/docker/docker/api/swagger.yaml +++ b/vendor/github.com/docker/docker/api/swagger.yaml @@ -2234,6 +2234,10 @@ definitions: password: type: "string" email: + description: | + Email is an optional value associated with the username. + + > **Deprecated**: This field is deprecated since docker 1.11 (API v1.23) and will be removed in a future release. type: "string" serveraddress: type: "string" @@ -4392,6 +4396,7 @@ definitions: A counter that triggers an update even if no relevant parameters have been changed. type: "integer" + format: "uint64" Runtime: description: | Runtime is the type of runtime specified for the task executor. diff --git a/vendor/github.com/docker/docker/api/types/build/disk_usage.go b/vendor/github.com/docker/docker/api/types/build/disk_usage.go index e969b6d615f2..cfd7333272c4 100644 --- a/vendor/github.com/docker/docker/api/types/build/disk_usage.go +++ b/vendor/github.com/docker/docker/api/types/build/disk_usage.go @@ -1,6 +1,8 @@ package build // CacheDiskUsage contains disk usage for the build cache. +// +// Deprecated: this type is no longer used and will be removed in the next release. type CacheDiskUsage struct { TotalSize int64 Reclaimable int64 diff --git a/vendor/github.com/docker/docker/api/types/container/disk_usage.go b/vendor/github.com/docker/docker/api/types/container/disk_usage.go index 05b6cbe9c709..d77538c2ab55 100644 --- a/vendor/github.com/docker/docker/api/types/container/disk_usage.go +++ b/vendor/github.com/docker/docker/api/types/container/disk_usage.go @@ -1,6 +1,8 @@ package container // DiskUsage contains disk usage for containers. +// +// Deprecated: this type is no longer used and will be removed in the next release. type DiskUsage struct { TotalSize int64 Reclaimable int64 diff --git a/vendor/github.com/docker/docker/api/types/filters/filters_deprecated.go b/vendor/github.com/docker/docker/api/types/filters/filters_deprecated.go new file mode 100644 index 000000000000..4504cd7a7fef --- /dev/null +++ b/vendor/github.com/docker/docker/api/types/filters/filters_deprecated.go @@ -0,0 +1,61 @@ +package filters + +import ( + "encoding/json" + + "github.com/docker/docker/api/types/versions" +) + +// ToParamWithVersion encodes Args as a JSON string. If version is less than 1.22 +// then the encoded format will use an older legacy format where the values are a +// list of strings, instead of a set. +// +// Deprecated: do not use in any new code; use ToJSON instead +func ToParamWithVersion(version string, a Args) (string, error) { + out, err := ToJSON(a) + if out == "" || err != nil { + return "", nil + } + if version != "" && versions.LessThan(version, "1.22") { + return encodeLegacyFilters(out) + } + return out, nil +} + +// encodeLegacyFilters encodes Args in the legacy format as used in API v1.21 and older. +// where values are a list of strings, instead of a set. +// +// Don't use in any new code; use [filters.ToJSON]] instead. +func encodeLegacyFilters(currentFormat string) (string, error) { + // The Args.fields field is not exported, but used to marshal JSON, + // so we'll marshal to the new format, then unmarshal to get the + // fields, and marshal again. + // + // This is far from optimal, but this code is only used for deprecated + // API versions, so should not be hit commonly. + var argsFields map[string]map[string]bool + err := json.Unmarshal([]byte(currentFormat), &argsFields) + if err != nil { + return "", err + } + + buf, err := json.Marshal(convertArgsToSlice(argsFields)) + if err != nil { + return "", err + } + return string(buf), nil +} + +func convertArgsToSlice(f map[string]map[string]bool) map[string][]string { + m := map[string][]string{} + for k, v := range f { + values := []string{} + for kk := range v { + if v[kk] { + values = append(values, kk) + } + } + m[k] = values + } + return m +} diff --git a/vendor/github.com/docker/docker/api/types/filters/parse.go b/vendor/github.com/docker/docker/api/types/filters/parse.go index 86f4bdb28e17..396657bb1921 100644 --- a/vendor/github.com/docker/docker/api/types/filters/parse.go +++ b/vendor/github.com/docker/docker/api/types/filters/parse.go @@ -8,8 +8,6 @@ import ( "encoding/json" "regexp" "strings" - - "github.com/docker/docker/api/types/versions" ) // Args stores a mapping of keys to a set of multiple values. @@ -63,24 +61,6 @@ func ToJSON(a Args) (string, error) { return string(buf), err } -// ToParamWithVersion encodes Args as a JSON string. If version is less than 1.22 -// then the encoded format will use an older legacy format where the values are a -// list of strings, instead of a set. -// -// Deprecated: do not use in any new code; use ToJSON instead -func ToParamWithVersion(version string, a Args) (string, error) { - if a.Len() == 0 { - return "", nil - } - - if version != "" && versions.LessThan(version, "1.22") { - buf, err := json.Marshal(convertArgsToSlice(a.fields)) - return string(buf), err - } - - return ToJSON(a) -} - // FromJSON decodes a JSON encoded string into Args func FromJSON(p string) (Args, error) { args := NewArgs() @@ -320,17 +300,3 @@ func deprecatedArgs(d map[string][]string) map[string]map[string]bool { } return m } - -func convertArgsToSlice(f map[string]map[string]bool) map[string][]string { - m := map[string][]string{} - for k, v := range f { - values := []string{} - for kk := range v { - if v[kk] { - values = append(values, kk) - } - } - m[k] = values - } - return m -} diff --git a/vendor/github.com/docker/docker/api/types/image/disk_usage.go b/vendor/github.com/docker/docker/api/types/image/disk_usage.go index b29d925cac48..e847386a8d13 100644 --- a/vendor/github.com/docker/docker/api/types/image/disk_usage.go +++ b/vendor/github.com/docker/docker/api/types/image/disk_usage.go @@ -1,6 +1,8 @@ package image // DiskUsage contains disk usage for images. +// +// Deprecated: this type is no longer used and will be removed in the next release. type DiskUsage struct { TotalSize int64 Reclaimable int64 diff --git a/vendor/github.com/docker/docker/api/types/network/endpoint.go b/vendor/github.com/docker/docker/api/types/network/endpoint.go index 167ac70ab56a..cdc06c6c9001 100644 --- a/vendor/github.com/docker/docker/api/types/network/endpoint.go +++ b/vendor/github.com/docker/docker/api/types/network/endpoint.go @@ -4,8 +4,6 @@ import ( "errors" "fmt" "net" - - "github.com/docker/docker/internal/multierror" ) // EndpointSettings stores the network endpoint details @@ -99,7 +97,7 @@ func (cfg *EndpointIPAMConfig) IsInRange(v4Subnets []NetworkSubnet, v6Subnets [] errs = append(errs, err) } - return multierror.Join(errs...) + return errJoin(errs...) } func validateEndpointIPAddress(epAddr string, ipamSubnets []NetworkSubnet) error { @@ -149,5 +147,5 @@ func (cfg *EndpointIPAMConfig) Validate() error { } } - return multierror.Join(errs...) + return errJoin(errs...) } diff --git a/vendor/github.com/docker/docker/api/types/network/ipam.go b/vendor/github.com/docker/docker/api/types/network/ipam.go index f319e1402b08..f9a9ff9b358f 100644 --- a/vendor/github.com/docker/docker/api/types/network/ipam.go +++ b/vendor/github.com/docker/docker/api/types/network/ipam.go @@ -4,8 +4,7 @@ import ( "errors" "fmt" "net/netip" - - "github.com/docker/docker/internal/multierror" + "strings" ) // IPAM represents IP Address Management @@ -72,7 +71,7 @@ func ValidateIPAM(ipam *IPAM, enableIPv6 bool) error { } } - if err := multierror.Join(errs...); err != nil { + if err := errJoin(errs...); err != nil { return fmt.Errorf("invalid network config:\n%w", err) } @@ -132,3 +131,43 @@ func validateAddress(address string, subnet netip.Prefix, subnetFamily ipFamily) return nil } + +func errJoin(errs ...error) error { + n := 0 + for _, err := range errs { + if err != nil { + n++ + } + } + if n == 0 { + return nil + } + e := &joinError{ + errs: make([]error, 0, n), + } + for _, err := range errs { + if err != nil { + e.errs = append(e.errs, err) + } + } + return e +} + +type joinError struct { + errs []error +} + +func (e *joinError) Error() string { + if len(e.errs) == 1 { + return strings.TrimSpace(e.errs[0].Error()) + } + stringErrs := make([]string, 0, len(e.errs)) + for _, subErr := range e.errs { + stringErrs = append(stringErrs, strings.ReplaceAll(subErr.Error(), "\n", "\n\t")) + } + return "* " + strings.Join(stringErrs, "\n* ") +} + +func (e *joinError) Unwrap() []error { + return e.errs +} diff --git a/vendor/github.com/docker/docker/api/types/registry/authconfig.go b/vendor/github.com/docker/docker/api/types/registry/authconfig.go index fa9037bdadfd..4c6d7ab2badf 100644 --- a/vendor/github.com/docker/docker/api/types/registry/authconfig.go +++ b/vendor/github.com/docker/docker/api/types/registry/authconfig.go @@ -32,8 +32,8 @@ type AuthConfig struct { Auth string `json:"auth,omitempty"` // Email is an optional value associated with the username. - // This field is deprecated and will be removed in a later - // version of docker. + // + // Deprecated: This field is deprecated since docker 1.11 (API v1.23) and will be removed in the next release. Email string `json:"email,omitempty"` ServerAddress string `json:"serveraddress,omitempty"` diff --git a/vendor/github.com/docker/docker/api/types/swarm/runtime.go b/vendor/github.com/docker/docker/api/types/swarm/runtime.go index 8a28320f7b85..3fda4ca65d1d 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/runtime.go +++ b/vendor/github.com/docker/docker/api/types/swarm/runtime.go @@ -1,5 +1,7 @@ package swarm +import "github.com/docker/docker/api/types/swarm/runtime" + // RuntimeType is the type of runtime used for the TaskSpec type RuntimeType string @@ -25,3 +27,11 @@ const ( type NetworkAttachmentSpec struct { ContainerID string } + +// RuntimeSpec defines the base payload which clients can specify for creating +// a service with the plugin runtime. +type RuntimeSpec = runtime.PluginSpec + +// RuntimePrivilege describes a permission the user has to accept +// upon installing a plugin. +type RuntimePrivilege = runtime.PluginPrivilege diff --git a/vendor/github.com/docker/docker/api/types/swarm/runtime/gen.go b/vendor/github.com/docker/docker/api/types/swarm/runtime/gen.go deleted file mode 100644 index 90e572cf9c90..000000000000 --- a/vendor/github.com/docker/docker/api/types/swarm/runtime/gen.go +++ /dev/null @@ -1,3 +0,0 @@ -//go:generate protoc --gogofaster_out=import_path=github.com/docker/docker/api/types/swarm/runtime:. plugin.proto - -package runtime diff --git a/vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.pb.go b/vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.pb.go deleted file mode 100644 index 32aaf0d51990..000000000000 --- a/vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.pb.go +++ /dev/null @@ -1,808 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: plugin.proto - -package runtime - -import ( - fmt "fmt" - proto "github.com/gogo/protobuf/proto" - io "io" - math "math" - math_bits "math/bits" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -// PluginSpec defines the base payload which clients can specify for creating -// a service with the plugin runtime. -type PluginSpec struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Remote string `protobuf:"bytes,2,opt,name=remote,proto3" json:"remote,omitempty"` - Privileges []*PluginPrivilege `protobuf:"bytes,3,rep,name=privileges,proto3" json:"privileges,omitempty"` - Disabled bool `protobuf:"varint,4,opt,name=disabled,proto3" json:"disabled,omitempty"` - Env []string `protobuf:"bytes,5,rep,name=env,proto3" json:"env,omitempty"` -} - -func (m *PluginSpec) Reset() { *m = PluginSpec{} } -func (m *PluginSpec) String() string { return proto.CompactTextString(m) } -func (*PluginSpec) ProtoMessage() {} -func (*PluginSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_22a625af4bc1cc87, []int{0} -} -func (m *PluginSpec) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *PluginSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_PluginSpec.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *PluginSpec) XXX_Merge(src proto.Message) { - xxx_messageInfo_PluginSpec.Merge(m, src) -} -func (m *PluginSpec) XXX_Size() int { - return m.Size() -} -func (m *PluginSpec) XXX_DiscardUnknown() { - xxx_messageInfo_PluginSpec.DiscardUnknown(m) -} - -var xxx_messageInfo_PluginSpec proto.InternalMessageInfo - -func (m *PluginSpec) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *PluginSpec) GetRemote() string { - if m != nil { - return m.Remote - } - return "" -} - -func (m *PluginSpec) GetPrivileges() []*PluginPrivilege { - if m != nil { - return m.Privileges - } - return nil -} - -func (m *PluginSpec) GetDisabled() bool { - if m != nil { - return m.Disabled - } - return false -} - -func (m *PluginSpec) GetEnv() []string { - if m != nil { - return m.Env - } - return nil -} - -// PluginPrivilege describes a permission the user has to accept -// upon installing a plugin. -type PluginPrivilege struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` - Value []string `protobuf:"bytes,3,rep,name=value,proto3" json:"value,omitempty"` -} - -func (m *PluginPrivilege) Reset() { *m = PluginPrivilege{} } -func (m *PluginPrivilege) String() string { return proto.CompactTextString(m) } -func (*PluginPrivilege) ProtoMessage() {} -func (*PluginPrivilege) Descriptor() ([]byte, []int) { - return fileDescriptor_22a625af4bc1cc87, []int{1} -} -func (m *PluginPrivilege) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *PluginPrivilege) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_PluginPrivilege.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *PluginPrivilege) XXX_Merge(src proto.Message) { - xxx_messageInfo_PluginPrivilege.Merge(m, src) -} -func (m *PluginPrivilege) XXX_Size() int { - return m.Size() -} -func (m *PluginPrivilege) XXX_DiscardUnknown() { - xxx_messageInfo_PluginPrivilege.DiscardUnknown(m) -} - -var xxx_messageInfo_PluginPrivilege proto.InternalMessageInfo - -func (m *PluginPrivilege) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *PluginPrivilege) GetDescription() string { - if m != nil { - return m.Description - } - return "" -} - -func (m *PluginPrivilege) GetValue() []string { - if m != nil { - return m.Value - } - return nil -} - -func init() { - proto.RegisterType((*PluginSpec)(nil), "PluginSpec") - proto.RegisterType((*PluginPrivilege)(nil), "PluginPrivilege") -} - -func init() { proto.RegisterFile("plugin.proto", fileDescriptor_22a625af4bc1cc87) } - -var fileDescriptor_22a625af4bc1cc87 = []byte{ - // 225 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0xc8, 0x29, 0x4d, - 0xcf, 0xcc, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x9a, 0xc1, 0xc8, 0xc5, 0x15, 0x00, 0x16, - 0x08, 0x2e, 0x48, 0x4d, 0x16, 0x12, 0xe2, 0x62, 0xc9, 0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, 0x60, - 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x85, 0xc4, 0xb8, 0xd8, 0x8a, 0x52, 0x73, 0xf3, 0x4b, 0x52, 0x25, - 0x98, 0xc0, 0xa2, 0x50, 0x9e, 0x90, 0x01, 0x17, 0x57, 0x41, 0x51, 0x66, 0x59, 0x66, 0x4e, 0x6a, - 0x7a, 0x6a, 0xb1, 0x04, 0xb3, 0x02, 0xb3, 0x06, 0xb7, 0x91, 0x80, 0x1e, 0xc4, 0xb0, 0x00, 0x98, - 0x44, 0x10, 0x92, 0x1a, 0x21, 0x29, 0x2e, 0x8e, 0x94, 0xcc, 0xe2, 0xc4, 0xa4, 0x9c, 0xd4, 0x14, - 0x09, 0x16, 0x05, 0x46, 0x0d, 0x8e, 0x20, 0x38, 0x5f, 0x48, 0x80, 0x8b, 0x39, 0x35, 0xaf, 0x4c, - 0x82, 0x55, 0x81, 0x59, 0x83, 0x33, 0x08, 0xc4, 0x54, 0x8a, 0xe5, 0xe2, 0x47, 0x33, 0x0c, 0xab, - 0xf3, 0x14, 0xb8, 0xb8, 0x53, 0x52, 0x8b, 0x93, 0x8b, 0x32, 0x0b, 0x4a, 0x32, 0xf3, 0xf3, 0xa0, - 0x6e, 0x44, 0x16, 0x12, 0x12, 0xe1, 0x62, 0x2d, 0x4b, 0xcc, 0x29, 0x4d, 0x05, 0xbb, 0x91, 0x33, - 0x08, 0xc2, 0x71, 0x92, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, - 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36, - 0x70, 0xd0, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x37, 0xea, 0xe2, 0xca, 0x2a, 0x01, 0x00, - 0x00, -} - -func (m *PluginSpec) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *PluginSpec) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *PluginSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Env) > 0 { - for iNdEx := len(m.Env) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Env[iNdEx]) - copy(dAtA[i:], m.Env[iNdEx]) - i = encodeVarintPlugin(dAtA, i, uint64(len(m.Env[iNdEx]))) - i-- - dAtA[i] = 0x2a - } - } - if m.Disabled { - i-- - if m.Disabled { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x20 - } - if len(m.Privileges) > 0 { - for iNdEx := len(m.Privileges) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Privileges[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintPlugin(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - } - if len(m.Remote) > 0 { - i -= len(m.Remote) - copy(dAtA[i:], m.Remote) - i = encodeVarintPlugin(dAtA, i, uint64(len(m.Remote))) - i-- - dAtA[i] = 0x12 - } - if len(m.Name) > 0 { - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintPlugin(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *PluginPrivilege) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *PluginPrivilege) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *PluginPrivilege) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Value) > 0 { - for iNdEx := len(m.Value) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Value[iNdEx]) - copy(dAtA[i:], m.Value[iNdEx]) - i = encodeVarintPlugin(dAtA, i, uint64(len(m.Value[iNdEx]))) - i-- - dAtA[i] = 0x1a - } - } - if len(m.Description) > 0 { - i -= len(m.Description) - copy(dAtA[i:], m.Description) - i = encodeVarintPlugin(dAtA, i, uint64(len(m.Description))) - i-- - dAtA[i] = 0x12 - } - if len(m.Name) > 0 { - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintPlugin(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func encodeVarintPlugin(dAtA []byte, offset int, v uint64) int { - offset -= sovPlugin(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *PluginSpec) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - if l > 0 { - n += 1 + l + sovPlugin(uint64(l)) - } - l = len(m.Remote) - if l > 0 { - n += 1 + l + sovPlugin(uint64(l)) - } - if len(m.Privileges) > 0 { - for _, e := range m.Privileges { - l = e.Size() - n += 1 + l + sovPlugin(uint64(l)) - } - } - if m.Disabled { - n += 2 - } - if len(m.Env) > 0 { - for _, s := range m.Env { - l = len(s) - n += 1 + l + sovPlugin(uint64(l)) - } - } - return n -} - -func (m *PluginPrivilege) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - if l > 0 { - n += 1 + l + sovPlugin(uint64(l)) - } - l = len(m.Description) - if l > 0 { - n += 1 + l + sovPlugin(uint64(l)) - } - if len(m.Value) > 0 { - for _, s := range m.Value { - l = len(s) - n += 1 + l + sovPlugin(uint64(l)) - } - } - return n -} - -func sovPlugin(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozPlugin(x uint64) (n int) { - return sovPlugin(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *PluginSpec) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowPlugin - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: PluginSpec: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PluginSpec: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowPlugin - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthPlugin - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthPlugin - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Remote", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowPlugin - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthPlugin - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthPlugin - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Remote = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Privileges", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowPlugin - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthPlugin - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthPlugin - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Privileges = append(m.Privileges, &PluginPrivilege{}) - if err := m.Privileges[len(m.Privileges)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Disabled", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowPlugin - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.Disabled = bool(v != 0) - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Env", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowPlugin - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthPlugin - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthPlugin - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Env = append(m.Env, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipPlugin(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthPlugin - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *PluginPrivilege) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowPlugin - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: PluginPrivilege: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PluginPrivilege: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowPlugin - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthPlugin - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthPlugin - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowPlugin - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthPlugin - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthPlugin - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Description = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowPlugin - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthPlugin - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthPlugin - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Value = append(m.Value, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipPlugin(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthPlugin - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipPlugin(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowPlugin - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowPlugin - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowPlugin - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthPlugin - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupPlugin - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthPlugin - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthPlugin = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowPlugin = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupPlugin = fmt.Errorf("proto: unexpected end of group") -) diff --git a/vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.proto b/vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.proto deleted file mode 100644 index e311b36ba2cf..000000000000 --- a/vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.proto +++ /dev/null @@ -1,19 +0,0 @@ -syntax = "proto3"; - -// PluginSpec defines the base payload which clients can specify for creating -// a service with the plugin runtime. -message PluginSpec { - string name = 1; - string remote = 2; - repeated PluginPrivilege privileges = 3; - bool disabled = 4; - repeated string env = 5; -} - -// PluginPrivilege describes a permission the user has to accept -// upon installing a plugin. -message PluginPrivilege { - string name = 1; - string description = 2; - repeated string value = 3; -} diff --git a/vendor/github.com/docker/docker/api/types/swarm/runtime/runtime.go b/vendor/github.com/docker/docker/api/types/swarm/runtime/runtime.go new file mode 100644 index 000000000000..95176b268186 --- /dev/null +++ b/vendor/github.com/docker/docker/api/types/swarm/runtime/runtime.go @@ -0,0 +1,27 @@ +package runtime + +import "fmt" + +// PluginSpec defines the base payload which clients can specify for creating +// a service with the plugin runtime. +type PluginSpec struct { + Name string `json:"name,omitempty"` + Remote string `json:"remote,omitempty"` + Privileges []*PluginPrivilege `json:"privileges,omitempty"` + Disabled bool `json:"disabled,omitempty"` + Env []string `json:"env,omitempty"` +} + +// PluginPrivilege describes a permission the user has to accept +// upon installing a plugin. +type PluginPrivilege struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Value []string `json:"value,omitempty"` +} + +var ( + ErrInvalidLengthPlugin = fmt.Errorf("proto: negative length found during unmarshaling") // Deprecated: this error was only used internally and is no longer used. + ErrIntOverflowPlugin = fmt.Errorf("proto: integer overflow") // Deprecated: this error was only used internally and is no longer used. + ErrUnexpectedEndOfGroupPlugin = fmt.Errorf("proto: unexpected end of group") // Deprecated: this error was only used internally and is no longer used. +) diff --git a/vendor/github.com/docker/docker/api/types/swarm/task.go b/vendor/github.com/docker/docker/api/types/swarm/task.go index 4dc95e8b1dde..e143f844fa8f 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/task.go +++ b/vendor/github.com/docker/docker/api/types/swarm/task.go @@ -4,7 +4,6 @@ import ( "time" "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/swarm/runtime" ) // TaskState represents the state of a task. @@ -77,7 +76,7 @@ type TaskSpec struct { // NetworkAttachmentSpec is used if the `Runtime` field is set to // `attachment`. ContainerSpec *ContainerSpec `json:",omitempty"` - PluginSpec *runtime.PluginSpec `json:",omitempty"` + PluginSpec *RuntimeSpec `json:",omitempty"` NetworkAttachmentSpec *NetworkAttachmentSpec `json:",omitempty"` Resources *ResourceRequirements `json:",omitempty"` diff --git a/vendor/github.com/docker/docker/api/types/system/disk_usage.go b/vendor/github.com/docker/docker/api/types/system/disk_usage.go deleted file mode 100644 index 99078cf196d0..000000000000 --- a/vendor/github.com/docker/docker/api/types/system/disk_usage.go +++ /dev/null @@ -1,17 +0,0 @@ -package system - -import ( - "github.com/docker/docker/api/types/build" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/api/types/volume" -) - -// DiskUsage contains response of Engine API for API 1.49 and greater: -// GET "/system/df" -type DiskUsage struct { - Images *image.DiskUsage - Containers *container.DiskUsage - Volumes *volume.DiskUsage - BuildCache *build.CacheDiskUsage -} diff --git a/vendor/github.com/docker/docker/api/types/volume/disk_usage.go b/vendor/github.com/docker/docker/api/types/volume/disk_usage.go index 3d716c6e00d9..88974303a0b0 100644 --- a/vendor/github.com/docker/docker/api/types/volume/disk_usage.go +++ b/vendor/github.com/docker/docker/api/types/volume/disk_usage.go @@ -1,6 +1,8 @@ package volume // DiskUsage contains disk usage for volumes. +// +// Deprecated: this type is no longer used and will be removed in the next release. type DiskUsage struct { TotalSize int64 Reclaimable int64 diff --git a/vendor/github.com/docker/docker/client/client.go b/vendor/github.com/docker/docker/client/client.go index d6e014dddf81..8acfb7f490e4 100644 --- a/vendor/github.com/docker/docker/client/client.go +++ b/vendor/github.com/docker/docker/client/client.go @@ -463,7 +463,9 @@ func (cli *Client) dialer() func(context.Context) (net.Conn, error) { case "unix": return net.Dial(cli.proto, cli.addr) case "npipe": - return sockets.DialPipe(cli.addr, 32*time.Second) + ctx, cancel := context.WithTimeout(ctx, 32*time.Second) + defer cancel() + return dialPipeContext(ctx, cli.addr) default: if tlsConfig := cli.tlsConfig(); tlsConfig != nil { return tls.Dial(cli.proto, cli.addr, tlsConfig) diff --git a/vendor/github.com/docker/docker/client/client_unix.go b/vendor/github.com/docker/docker/client/client_unix.go index e5b921b40642..1fb9fbfb9e55 100644 --- a/vendor/github.com/docker/docker/client/client_unix.go +++ b/vendor/github.com/docker/docker/client/client_unix.go @@ -2,6 +2,17 @@ package client +import ( + "context" + "net" + "syscall" +) + // DefaultDockerHost defines OS-specific default host if the DOCKER_HOST // (EnvOverrideHost) environment variable is unset or empty. const DefaultDockerHost = "unix:///var/run/docker.sock" + +// dialPipeContext connects to a Windows named pipe. It is not supported on non-Windows. +func dialPipeContext(_ context.Context, _ string) (net.Conn, error) { + return nil, syscall.EAFNOSUPPORT +} diff --git a/vendor/github.com/docker/docker/client/client_windows.go b/vendor/github.com/docker/docker/client/client_windows.go index 19b954b2fd78..b471c0612403 100644 --- a/vendor/github.com/docker/docker/client/client_windows.go +++ b/vendor/github.com/docker/docker/client/client_windows.go @@ -1,5 +1,17 @@ package client +import ( + "context" + "net" + + "github.com/Microsoft/go-winio" +) + // DefaultDockerHost defines OS-specific default host if the DOCKER_HOST // (EnvOverrideHost) environment variable is unset or empty. const DefaultDockerHost = "npipe:////./pipe/docker_engine" + +// dialPipeContext connects to a Windows named pipe. It is not supported on non-Windows. +func dialPipeContext(ctx context.Context, addr string) (net.Conn, error) { + return winio.DialPipeContext(ctx, addr) +} diff --git a/vendor/github.com/docker/docker/client/container_stats.go b/vendor/github.com/docker/docker/client/container_stats.go index 2244e0f4b94a..076954f4c3e8 100644 --- a/vendor/github.com/docker/docker/client/container_stats.go +++ b/vendor/github.com/docker/docker/client/container_stats.go @@ -28,7 +28,7 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea return container.StatsResponseReader{ Body: resp.Body, - OSType: getDockerOS(resp.Header.Get("Server")), + OSType: resp.Header.Get("Ostype"), }, nil } @@ -51,6 +51,6 @@ func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string return container.StatsResponseReader{ Body: resp.Body, - OSType: getDockerOS(resp.Header.Get("Server")), + OSType: resp.Header.Get("Ostype"), }, nil } diff --git a/vendor/github.com/docker/docker/client/image_build.go b/vendor/github.com/docker/docker/client/image_build.go index 66ca75e4af28..1ed0878bfdb2 100644 --- a/vendor/github.com/docker/docker/client/image_build.go +++ b/vendor/github.com/docker/docker/client/image_build.go @@ -40,7 +40,7 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio return build.ImageBuildResponse{ Body: resp.Body, - OSType: getDockerOS(resp.Header.Get("Server")), + OSType: resp.Header.Get("Ostype"), }, nil } diff --git a/vendor/github.com/docker/docker/client/utils.go b/vendor/github.com/docker/docker/client/utils.go index 67e1e6934b59..7b82f185ac58 100644 --- a/vendor/github.com/docker/docker/client/utils.go +++ b/vendor/github.com/docker/docker/client/utils.go @@ -8,12 +8,9 @@ import ( cerrdefs "github.com/containerd/errdefs" "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/internal/lazyregexp" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) -var headerRegexp = lazyregexp.New(`\ADocker/.+\s\((.+)\)\z`) - type emptyIDError string func (e emptyIDError) InvalidParameter() {} @@ -31,16 +28,6 @@ func trimID(objType, id string) (string, error) { return id, nil } -// getDockerOS returns the operating system based on the server header from the daemon. -func getDockerOS(serverHeader string) string { - var osType string - matches := headerRegexp.FindStringSubmatch(serverHeader) - if len(matches) > 0 { - osType = matches[1] - } - return osType -} - // getFiltersQuery returns a url query with "filters" query term, based on the // filters provided. func getFiltersQuery(f filters.Args) (url.Values, error) { diff --git a/vendor/github.com/docker/docker/internal/lazyregexp/lazyregexp.go b/vendor/github.com/docker/docker/internal/lazyregexp/lazyregexp.go deleted file mode 100644 index 6334edb60dca..000000000000 --- a/vendor/github.com/docker/docker/internal/lazyregexp/lazyregexp.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Code below was largely copied from golang.org/x/mod@v0.22; -// https://github.com/golang/mod/blob/v0.22.0/internal/lazyregexp/lazyre.go -// with some additional methods added. - -// Package lazyregexp is a thin wrapper over regexp, allowing the use of global -// regexp variables without forcing them to be compiled at init. -package lazyregexp - -import ( - "os" - "regexp" - "strings" - "sync" -) - -// Regexp is a wrapper around [regexp.Regexp], where the underlying regexp will be -// compiled the first time it is needed. -type Regexp struct { - str string - once sync.Once - rx *regexp.Regexp -} - -func (r *Regexp) re() *regexp.Regexp { - r.once.Do(r.build) - return r.rx -} - -func (r *Regexp) build() { - r.rx = regexp.MustCompile(r.str) - r.str = "" -} - -func (r *Regexp) FindSubmatch(s []byte) [][]byte { - return r.re().FindSubmatch(s) -} - -func (r *Regexp) FindAllStringSubmatch(s string, n int) [][]string { - return r.re().FindAllStringSubmatch(s, n) -} - -func (r *Regexp) FindStringSubmatch(s string) []string { - return r.re().FindStringSubmatch(s) -} - -func (r *Regexp) FindStringSubmatchIndex(s string) []int { - return r.re().FindStringSubmatchIndex(s) -} - -func (r *Regexp) ReplaceAllString(src, repl string) string { - return r.re().ReplaceAllString(src, repl) -} - -func (r *Regexp) FindString(s string) string { - return r.re().FindString(s) -} - -func (r *Regexp) FindAllString(s string, n int) []string { - return r.re().FindAllString(s, n) -} - -func (r *Regexp) MatchString(s string) bool { - return r.re().MatchString(s) -} - -func (r *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string { - return r.re().ReplaceAllStringFunc(src, repl) -} - -func (r *Regexp) SubexpNames() []string { - return r.re().SubexpNames() -} - -var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test") - -// New creates a new lazy regexp, delaying the compiling work until it is first -// needed. If the code is being run as part of tests, the regexp compiling will -// happen immediately. -func New(str string) *Regexp { - lr := &Regexp{str: str} - if inTest { - // In tests, always compile the regexps early. - lr.re() - } - return lr -} diff --git a/vendor/github.com/docker/docker/internal/multierror/multierror.go b/vendor/github.com/docker/docker/internal/multierror/multierror.go deleted file mode 100644 index e899f4de85c9..000000000000 --- a/vendor/github.com/docker/docker/internal/multierror/multierror.go +++ /dev/null @@ -1,46 +0,0 @@ -package multierror - -import ( - "strings" -) - -// Join is a drop-in replacement for errors.Join with better formatting. -func Join(errs ...error) error { - n := 0 - for _, err := range errs { - if err != nil { - n++ - } - } - if n == 0 { - return nil - } - e := &joinError{ - errs: make([]error, 0, n), - } - for _, err := range errs { - if err != nil { - e.errs = append(e.errs, err) - } - } - return e -} - -type joinError struct { - errs []error -} - -func (e *joinError) Error() string { - if len(e.errs) == 1 { - return strings.TrimSpace(e.errs[0].Error()) - } - stringErrs := make([]string, 0, len(e.errs)) - for _, subErr := range e.errs { - stringErrs = append(stringErrs, strings.ReplaceAll(subErr.Error(), "\n", "\n\t")) - } - return "* " + strings.Join(stringErrs, "\n* ") -} - -func (e *joinError) Unwrap() []error { - return e.errs -} diff --git a/vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go b/vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go index 3d072808f1de..13df228c622c 100644 --- a/vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go +++ b/vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go @@ -151,9 +151,9 @@ type JSONMessage struct { // Deprecated: this field is deprecated since docker v0.7.1 / API v1.8. Use the information in [Progress] instead. This field will be omitted in a future release. ProgressMessage string `json:"progress,omitempty"` ID string `json:"id,omitempty"` - From string `json:"from,omitempty"` - Time int64 `json:"time,omitempty"` - TimeNano int64 `json:"timeNano,omitempty"` + From string `json:"from,omitempty"` // Deprecated: this field is no longer set in stream responses and should not be used. + Time int64 `json:"time,omitempty"` // Deprecated: this field is no longer set in stream responses and should not be used. + TimeNano int64 `json:"timeNano,omitempty"` // Deprecated: this field is no longer set in stream responses and should not be used. Error *JSONError `json:"errorDetail,omitempty"` // ErrorMessage contains errors encountered during the operation. diff --git a/vendor/modules.txt b/vendor/modules.txt index 61a2ba389c02..a98f67d7e4c0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/uuid -# github.com/docker/docker v28.3.3+incompatible +# github.com/docker/docker v28.3.4-0.20250828134822-02b4a1a3decc+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types @@ -90,8 +90,6 @@ github.com/docker/docker/api/types/time github.com/docker/docker/api/types/versions github.com/docker/docker/api/types/volume github.com/docker/docker/client -github.com/docker/docker/internal/lazyregexp -github.com/docker/docker/internal/multierror github.com/docker/docker/pkg/jsonmessage github.com/docker/docker/pkg/progress github.com/docker/docker/pkg/stdcopy From 4caa99468d6f46fd14df7071170d2f17220bbbfc Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 28 Aug 2025 15:52:47 +0200 Subject: [PATCH 120/193] [28.x] bump version to v28.4.0-dev This file is only used as default if no version is specified. We should probably get rid of this, but let's update it to better reflect the version that developer builds are building. https://github.com/docker/cli/blob/d48fb9f9f7bdb6e0ef37dbde68612a1704cad46e/docker.Makefile#L22 Signed-off-by: Sebastiaan van Stijn --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index af15bc84df1f..5440ff0cd4c6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -28.3.0-dev +28.4.0-dev From 0a2eaa4d05353b298789d9a931a9402456f699a8 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Tue, 26 Aug 2025 13:56:04 +0200 Subject: [PATCH 121/193] Add escape hatch for GODEBUG=x509negativeserial MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 7d7a7aac4d5d19cdce6abb488329f89b5860f40b) Signed-off-by: Paweł Gronowski --- cli/command/cli.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/cli/command/cli.go b/cli/command/cli.go index 1e042ec0e23b..c845de40a99d 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -282,6 +282,8 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) } filterResourceAttributesEnvvar() + cli.setAllowNegativex509() + return nil } @@ -475,6 +477,43 @@ func (cli *DockerCli) getDockerEndPoint() (ep docker.Endpoint, err error) { return resolveDockerEndpoint(cli.contextStore, cn) } +// setAllowNegativex509 is an escape hatch that sets the GODEBUG=x509negativeserial +// environment variable for this process and sub-processes (such as CLI plugins) +func (cli *DockerCli) setAllowNegativex509() { + cn := cli.CurrentContext() + meta, err := cli.ContextStore().GetMetadata(cn) + if err != nil { + return + } + + fieldName := "allowx509negativeserialdonotuse" + + var config any + var ok bool + switch m := meta.Metadata.(type) { + case DockerContext: + config, ok = m.AdditionalFields[fieldName] + if !ok { + return + } + case map[string]any: + config, ok = m[fieldName] + if !ok { + return + } + default: + return + } + + v, ok := config.(string) + if !ok { + return + } + if v == "1" { + _ = os.Setenv("GODEBUG", "x509negativeserial=1") + } +} + func (cli *DockerCli) initialize() error { cli.init.Do(func() { cli.dockerEndpoint, cli.initErr = cli.getDockerEndPoint() From 7c34fd56d05f39dab064de2bfdd36ace36afe033 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Fri, 29 Aug 2025 12:15:29 +0200 Subject: [PATCH 122/193] Cleanup setAllowNegativex509 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 65a6c35d902b950068bc7017e7bf1cb1f457a34e) Signed-off-by: Paweł Gronowski --- cli/command/cli.go | 49 +++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/cli/command/cli.go b/cli/command/cli.go index c845de40a99d..61d27e70fde8 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -282,7 +282,10 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) } filterResourceAttributesEnvvar() - cli.setAllowNegativex509() + meta, err := cli.contextStore.GetMetadata(cli.currentContext) + if err == nil { + setAllowNegativex509(meta) + } return nil } @@ -477,27 +480,42 @@ func (cli *DockerCli) getDockerEndPoint() (ep docker.Endpoint, err error) { return resolveDockerEndpoint(cli.contextStore, cn) } -// setAllowNegativex509 is an escape hatch that sets the GODEBUG=x509negativeserial -// environment variable for this process and sub-processes (such as CLI plugins) -func (cli *DockerCli) setAllowNegativex509() { - cn := cli.CurrentContext() - meta, err := cli.ContextStore().GetMetadata(cn) - if err != nil { +// setAllowNegativex509 is an escape hatch that sets the GODEBUG environment +// variable value using docker context metadata. +// +// { +// "Name": "my-context", +// "Metadata": { "GODEBUG": "x509negativeserial=1" } +// } +// +// WARNING: Setting x509negativeserial=1 allows Go's x509 library to accept +// X.509 certificates with negative serial numbers. +// This behavior is deprecated and non-compliant with current security +// standards (RFC 5280). Accepting negative serial numbers can introduce +// serious security vulnerabilities, including the risk of certificate +// collision or bypass attacks. +// This option should only be used for legacy compatibility and never in +// production environments. +// Use at your own risk. +func setAllowNegativex509(meta store.Metadata) { + fieldName := "GODEBUG" + godebugEnv := os.Getenv(fieldName) + // early return if GODEBUG is already set. We don't want to override what + // the user already sets. + if godebugEnv != "" { return } - fieldName := "allowx509negativeserialdonotuse" - - var config any + var cfg any var ok bool switch m := meta.Metadata.(type) { case DockerContext: - config, ok = m.AdditionalFields[fieldName] + cfg, ok = m.AdditionalFields[fieldName] if !ok { return } case map[string]any: - config, ok = m[fieldName] + cfg, ok = m[fieldName] if !ok { return } @@ -505,13 +523,12 @@ func (cli *DockerCli) setAllowNegativex509() { return } - v, ok := config.(string) + v, ok := cfg.(string) if !ok { return } - if v == "1" { - _ = os.Setenv("GODEBUG", "x509negativeserial=1") - } + // set the GODEBUG environment variable with whatever was in the context + _ = os.Setenv(fieldName, v) } func (cli *DockerCli) initialize() error { From 8cfe1f712e03c50e2c47776deed36e82279cd0b0 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Fri, 29 Aug 2025 12:15:49 +0200 Subject: [PATCH 123/193] Test setAllowNegativex509 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 467305fcea0c8a66f18305028bbaecd9d60cf7c4) Signed-off-by: Paweł Gronowski --- cli/command/cli_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index ef299d84f2df..338dae34dffc 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -18,6 +18,7 @@ import ( "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/context/store" "github.com/docker/cli/cli/flags" "github.com/docker/docker/api" "github.com/docker/docker/api/types" @@ -353,3 +354,23 @@ func TestHooksEnabled(t *testing.T) { assert.Check(t, !cli.HooksEnabled()) }) } + +func TestAllowNegativex509(t *testing.T) { + t.Run("GODEBUG already set", func(t *testing.T) { + t.Setenv("GODEBUG", "val1,val2") + meta := store.Metadata{} + setAllowNegativex509(meta) + assert.Equal(t, "val1,val2", os.Getenv("GODEBUG")) + }) + t.Run("GODEBUG in context metadata can set env", func(t *testing.T) { + meta := store.Metadata{ + Metadata: DockerContext{ + AdditionalFields: map[string]any{ + "GODEBUG": "val1,val2=1", + }, + }, + } + setAllowNegativex509(meta) + assert.Equal(t, "val1,val2=1", os.Getenv("GODEBUG")) + }) +} From 4108febfae7bb56d0478b4e73d4200fe7b37097a Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:08:15 +0200 Subject: [PATCH 124/193] rename function to fit what it is doing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 6163c03b112f1979f7bf4be1b3b0c1b3600adb68) Signed-off-by: Paweł Gronowski --- cli/command/cli.go | 6 +++--- cli/command/cli_test.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/command/cli.go b/cli/command/cli.go index 61d27e70fde8..d11961e61baa 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -284,7 +284,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) meta, err := cli.contextStore.GetMetadata(cli.currentContext) if err == nil { - setAllowNegativex509(meta) + setGoDebug(meta) } return nil @@ -480,7 +480,7 @@ func (cli *DockerCli) getDockerEndPoint() (ep docker.Endpoint, err error) { return resolveDockerEndpoint(cli.contextStore, cn) } -// setAllowNegativex509 is an escape hatch that sets the GODEBUG environment +// setGoDebug is an escape hatch that sets the GODEBUG environment // variable value using docker context metadata. // // { @@ -497,7 +497,7 @@ func (cli *DockerCli) getDockerEndPoint() (ep docker.Endpoint, err error) { // This option should only be used for legacy compatibility and never in // production environments. // Use at your own risk. -func setAllowNegativex509(meta store.Metadata) { +func setGoDebug(meta store.Metadata) { fieldName := "GODEBUG" godebugEnv := os.Getenv(fieldName) // early return if GODEBUG is already set. We don't want to override what diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index 338dae34dffc..cc30aed92689 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -355,11 +355,11 @@ func TestHooksEnabled(t *testing.T) { }) } -func TestAllowNegativex509(t *testing.T) { +func TestSetGoDebug(t *testing.T) { t.Run("GODEBUG already set", func(t *testing.T) { t.Setenv("GODEBUG", "val1,val2") meta := store.Metadata{} - setAllowNegativex509(meta) + setGoDebug(meta) assert.Equal(t, "val1,val2", os.Getenv("GODEBUG")) }) t.Run("GODEBUG in context metadata can set env", func(t *testing.T) { @@ -370,7 +370,7 @@ func TestAllowNegativex509(t *testing.T) { }, }, } - setAllowNegativex509(meta) + setGoDebug(meta) assert.Equal(t, "val1,val2=1", os.Getenv("GODEBUG")) }) } From b9e15efde0538452ce1a65b3e5e8afd6ffb14c79 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:31:21 +0200 Subject: [PATCH 125/193] return early if GODEBUG set or context is default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 72f79333e54b14c5a3309655d3b408352dc92078) Signed-off-by: Paweł Gronowski --- cli/command/cli.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/command/cli.go b/cli/command/cli.go index d11961e61baa..85fac565024b 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -282,6 +282,12 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) } filterResourceAttributesEnvvar() + // early return if GODEBUG is already set or the docker context is + // the default context, i.e. is a virtual context where we won't override + // any GODEBUG values. + if v := os.Getenv("GODEBUG"); cli.currentContext == DefaultContextName || v != "" { + return nil + } meta, err := cli.contextStore.GetMetadata(cli.currentContext) if err == nil { setGoDebug(meta) From 925db59377d0c8107f738dc4cc94ba8e7870d419 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sat, 30 Aug 2025 00:44:43 +0200 Subject: [PATCH 126/193] cli/command/context: deprecate exported types and functions These functions and types are shallow wrappers around the context store and were intended for internal use as implementation for the CLI itself. They were exported in 3126920af14ea5127e00a2f8f9d8e07a6c3f6ff9 to be used by plugins and Docker Desktop. However, there's currently no public uses of this, and Docker Desktop does not use these functions. This patch deprecates the exported functions as they were meant to be implementation specific for the CLI. If there's a need to provide utilities for manipulating the context-store other than through the CLI itself, we can consider creating an SDK for that purpose. This deprecates: - `RunCreate` and `CreateOptions` - `RunExport` and `ExportOptions` - `RunImport` - `RunRemove` and `RemoveOptions` - `RunUpdate` and `UpdateOptions` - `RunUse` Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 95eeafa5514aea3ebe00b9f19c86c097a9eaf0b6) Signed-off-by: Sebastiaan van Stijn --- cli/command/context/create.go | 80 ++++++++++++++------ cli/command/context/create_test.go | 92 +++++++++++------------ cli/command/context/export-import_test.go | 16 ++-- cli/command/context/export.go | 29 ++++--- cli/command/context/import.go | 21 ++++-- cli/command/context/list_test.go | 8 +- cli/command/context/remove.go | 24 ++++-- cli/command/context/remove_test.go | 12 +-- cli/command/context/update.go | 51 +++++++++---- cli/command/context/update_test.go | 30 ++++---- cli/command/context/use.go | 9 ++- cli/command/context/use_test.go | 18 ++--- 12 files changed, 238 insertions(+), 152 deletions(-) diff --git a/cli/command/context/create.go b/cli/command/context/create.go index 308aa8720069..67d9c0953868 100644 --- a/cli/command/context/create.go +++ b/cli/command/context/create.go @@ -19,6 +19,8 @@ import ( ) // CreateOptions are the options used for creating a context +// +// Deprecated: this type was for internal use and will be removed in the next release. type CreateOptions struct { Name string Description string @@ -30,6 +32,18 @@ type CreateOptions struct { metaData map[string]any } +// createOptions are the options used for creating a context +type createOptions struct { + name string + description string + endpoint map[string]string + from string + + // Additional Metadata to store in the context. This option is not + // currently exposed to the user. + metaData map[string]any +} + func longCreateDescription() string { buf := bytes.NewBuffer(nil) buf.WriteString("Create a context\n\nDocker endpoint config:\n\n") @@ -44,52 +58,68 @@ func longCreateDescription() string { } func newCreateCommand(dockerCLI command.Cli) *cobra.Command { - opts := &CreateOptions{} + opts := createOptions{} cmd := &cobra.Command{ Use: "create [OPTIONS] CONTEXT", Short: "Create a context", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.Name = args[0] - return RunCreate(dockerCLI, opts) + opts.name = args[0] + return runCreate(dockerCLI, &opts) }, Long: longCreateDescription(), ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() - flags.StringVar(&opts.Description, "description", "", "Description of the context") - flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint") - flags.StringVar(&opts.From, "from", "", "create context from a named context") + flags.StringVar(&opts.description, "description", "", "Description of the context") + flags.StringToStringVar(&opts.endpoint, "docker", nil, "set the docker endpoint") + flags.StringVar(&opts.from, "from", "", "create context from a named context") return cmd } // RunCreate creates a Docker context + +// Deprecated: this function was for internal use and will be removed in the next release. func RunCreate(dockerCLI command.Cli, o *CreateOptions) error { + if o == nil { + o = &CreateOptions{} + } + + return runCreate(dockerCLI, &createOptions{ + name: o.Name, + description: o.Description, + endpoint: o.Docker, + metaData: o.metaData, + }) +} + +// runCreate creates a Docker context +func runCreate(dockerCLI command.Cli, opts *createOptions) error { s := dockerCLI.ContextStore() - err := checkContextNameForCreation(s, o.Name) + err := checkContextNameForCreation(s, opts.name) if err != nil { return err } switch { - case o.From == "" && o.Docker == nil: - err = createFromExistingContext(s, dockerCLI.CurrentContext(), o) - case o.From != "": - err = createFromExistingContext(s, o.From, o) + case opts.from == "" && opts.endpoint == nil: + err = createFromExistingContext(s, dockerCLI.CurrentContext(), opts) + case opts.from != "": + err = createFromExistingContext(s, opts.from, opts) default: - err = createNewContext(s, o) + err = createNewContext(s, opts) } if err == nil { - _, _ = fmt.Fprintln(dockerCLI.Out(), o.Name) - _, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully created context %q\n", o.Name) + _, _ = fmt.Fprintln(dockerCLI.Out(), opts.name) + _, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully created context %q\n", opts.name) } return err } -func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error { - if o.Docker == nil { +func createNewContext(contextStore store.ReaderWriter, opts *createOptions) error { + if opts.endpoint == nil { return errors.New("docker endpoint configuration is required") } - dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(contextStore, o.Docker) + dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(contextStore, opts.endpoint) if err != nil { return fmt.Errorf("unable to create docker endpoint config: %w", err) } @@ -98,10 +128,10 @@ func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error { docker.DockerEndpoint: dockerEP, }, Metadata: command.DockerContext{ - Description: o.Description, - AdditionalFields: o.metaData, + Description: opts.description, + AdditionalFields: opts.metaData, }, - Name: o.Name, + Name: opts.name, } contextTLSData := store.ContextTLSData{} if dockerTLS != nil { @@ -115,7 +145,7 @@ func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error { if err := contextStore.CreateOrUpdate(contextMetadata); err != nil { return err } - return contextStore.ResetTLSMaterial(o.Name, &contextTLSData) + return contextStore.ResetTLSMaterial(opts.name, &contextTLSData) } func checkContextNameForCreation(s store.Reader, name string) error { @@ -131,16 +161,16 @@ func checkContextNameForCreation(s store.Reader, name string) error { return nil } -func createFromExistingContext(s store.ReaderWriter, fromContextName string, o *CreateOptions) error { - if len(o.Docker) != 0 { +func createFromExistingContext(s store.ReaderWriter, fromContextName string, opts *createOptions) error { + if len(opts.endpoint) != 0 { return errors.New("cannot use --docker flag when --from is set") } reader := store.Export(fromContextName, &descriptionDecorator{ Reader: s, - description: o.Description, + description: opts.description, }) defer reader.Close() - return store.Import(o.Name, s, reader) + return store.Import(opts.name, s, reader) } type descriptionDecorator struct { diff --git a/cli/command/context/create_test.go b/cli/command/context/create_test.go index 794f95de04d5..b3344dc9e94b 100644 --- a/cli/command/context/create_test.go +++ b/cli/command/context/create_test.go @@ -60,7 +60,7 @@ func TestCreate(t *testing.T) { assert.NilError(t, cli.ContextStore().CreateOrUpdate(store.Metadata{Name: "existing-context"})) tests := []struct { doc string - options CreateOptions + options createOptions expecterErr string }{ { @@ -69,30 +69,30 @@ func TestCreate(t *testing.T) { }, { doc: "reserved name", - options: CreateOptions{ - Name: "default", + options: createOptions{ + name: "default", }, expecterErr: `"default" is a reserved context name`, }, { doc: "whitespace-only name", - options: CreateOptions{ - Name: " ", + options: createOptions{ + name: " ", }, expecterErr: `context name " " is invalid`, }, { doc: "existing context", - options: CreateOptions{ - Name: "existing-context", + options: createOptions{ + name: "existing-context", }, expecterErr: `context "existing-context" already exists`, }, { doc: "invalid docker host", - options: CreateOptions{ - Name: "invalid-docker-host", - Docker: map[string]string{ + options: createOptions{ + name: "invalid-docker-host", + endpoint: map[string]string{ "host": "some///invalid/host", }, }, @@ -100,27 +100,27 @@ func TestCreate(t *testing.T) { }, { doc: "ssh host with skip-tls-verify=false", - options: CreateOptions{ - Name: "skip-tls-verify-false", - Docker: map[string]string{ + options: createOptions{ + name: "skip-tls-verify-false", + endpoint: map[string]string{ "host": "ssh://example.com,skip-tls-verify=false", }, }, }, { doc: "ssh host with skip-tls-verify=true", - options: CreateOptions{ - Name: "skip-tls-verify-true", - Docker: map[string]string{ + options: createOptions{ + name: "skip-tls-verify-true", + endpoint: map[string]string{ "host": "ssh://example.com,skip-tls-verify=true", }, }, }, { doc: "ssh host with skip-tls-verify=INVALID", - options: CreateOptions{ - Name: "skip-tls-verify-invalid", - Docker: map[string]string{ + options: createOptions{ + name: "skip-tls-verify-invalid", + endpoint: map[string]string{ "host": "ssh://example.com", "skip-tls-verify": "INVALID", }, @@ -129,9 +129,9 @@ func TestCreate(t *testing.T) { }, { doc: "unknown option", - options: CreateOptions{ - Name: "unknown-option", - Docker: map[string]string{ + options: createOptions{ + name: "unknown-option", + endpoint: map[string]string{ "UNKNOWN": "value", }, }, @@ -140,7 +140,7 @@ func TestCreate(t *testing.T) { } for _, tc := range tests { t.Run(tc.doc, func(t *testing.T) { - err := RunCreate(cli, &tc.options) + err := runCreate(cli, &tc.options) if tc.expecterErr == "" { assert.NilError(t, err) } else { @@ -159,9 +159,9 @@ func assertContextCreateLogging(t *testing.T, cli *test.FakeCli, n string) { func TestCreateOrchestratorEmpty(t *testing.T) { cli := makeFakeCli(t) - err := RunCreate(cli, &CreateOptions{ - Name: "test", - Docker: map[string]string{}, + err := runCreate(cli, &createOptions{ + name: "test", + endpoint: map[string]string{}, }) assert.NilError(t, err) assertContextCreateLogging(t, cli, "test") @@ -187,20 +187,20 @@ func TestCreateFromContext(t *testing.T) { cli := makeFakeCli(t) cli.ResetOutputBuffers() - assert.NilError(t, RunCreate(cli, &CreateOptions{ - Name: "original", - Description: "original description", - Docker: map[string]string{ + assert.NilError(t, runCreate(cli, &createOptions{ + name: "original", + description: "original description", + endpoint: map[string]string{ keyHost: "tcp://42.42.42.42:2375", }, })) assertContextCreateLogging(t, cli, "original") cli.ResetOutputBuffers() - assert.NilError(t, RunCreate(cli, &CreateOptions{ - Name: "dummy", - Description: "dummy description", - Docker: map[string]string{ + assert.NilError(t, runCreate(cli, &createOptions{ + name: "dummy", + description: "dummy description", + endpoint: map[string]string{ keyHost: "tcp://24.24.24.24:2375", }, })) @@ -211,11 +211,11 @@ func TestCreateFromContext(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { cli.ResetOutputBuffers() - err := RunCreate(cli, &CreateOptions{ - From: "original", - Name: tc.name, - Description: tc.description, - Docker: tc.docker, + err := runCreate(cli, &createOptions{ + from: "original", + name: tc.name, + description: tc.description, + endpoint: tc.docker, }) assert.NilError(t, err) assertContextCreateLogging(t, cli, tc.name) @@ -251,10 +251,10 @@ func TestCreateFromCurrent(t *testing.T) { cli := makeFakeCli(t) cli.ResetOutputBuffers() - assert.NilError(t, RunCreate(cli, &CreateOptions{ - Name: "original", - Description: "original description", - Docker: map[string]string{ + assert.NilError(t, runCreate(cli, &createOptions{ + name: "original", + description: "original description", + endpoint: map[string]string{ keyHost: "tcp://42.42.42.42:2375", }, })) @@ -265,9 +265,9 @@ func TestCreateFromCurrent(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { cli.ResetOutputBuffers() - err := RunCreate(cli, &CreateOptions{ - Name: tc.name, - Description: tc.description, + err := runCreate(cli, &createOptions{ + name: tc.name, + description: tc.description, }) assert.NilError(t, err) assertContextCreateLogging(t, cli, tc.name) diff --git a/cli/command/context/export-import_test.go b/cli/command/context/export-import_test.go index 9aabd6ab4490..cfbac778ad6f 100644 --- a/cli/command/context/export-import_test.go +++ b/cli/command/context/export-import_test.go @@ -21,14 +21,11 @@ func TestExportImportWithFile(t *testing.T) { "MyCustomMetadata": t.Name(), }) cli.ErrBuffer().Reset() - assert.NilError(t, RunExport(cli, &ExportOptions{ - ContextName: "test", - Dest: contextFile, - })) + assert.NilError(t, runExport(cli, "test", contextFile)) assert.Equal(t, cli.ErrBuffer().String(), fmt.Sprintf("Written file %q\n", contextFile)) cli.OutBuffer().Reset() cli.ErrBuffer().Reset() - assert.NilError(t, RunImport(cli, "test2", contextFile)) + assert.NilError(t, runImport(cli, "test2", contextFile)) context1, err := cli.ContextStore().GetMetadata("test") assert.NilError(t, err) context2, err := cli.ContextStore().GetMetadata("test2") @@ -55,15 +52,12 @@ func TestExportImportPipe(t *testing.T) { }) cli.ErrBuffer().Reset() cli.OutBuffer().Reset() - assert.NilError(t, RunExport(cli, &ExportOptions{ - ContextName: "test", - Dest: "-", - })) + assert.NilError(t, runExport(cli, "test", "-")) assert.Equal(t, cli.ErrBuffer().String(), "") cli.SetIn(streams.NewIn(io.NopCloser(bytes.NewBuffer(cli.OutBuffer().Bytes())))) cli.OutBuffer().Reset() cli.ErrBuffer().Reset() - assert.NilError(t, RunImport(cli, "test2", "-")) + assert.NilError(t, runImport(cli, "test2", "-")) context1, err := cli.ContextStore().GetMetadata("test") assert.NilError(t, err) context2, err := cli.ContextStore().GetMetadata("test2") @@ -88,6 +82,6 @@ func TestExportExistingFile(t *testing.T) { cli := makeFakeCli(t) cli.ErrBuffer().Reset() assert.NilError(t, os.WriteFile(contextFile, []byte{}, 0o644)) - err := RunExport(cli, &ExportOptions{ContextName: "test", Dest: contextFile}) + err := runExport(cli, "test", contextFile) assert.Assert(t, os.IsExist(err)) } diff --git a/cli/command/context/export.go b/cli/command/context/export.go index 96e35b67505f..90d5b723203d 100644 --- a/cli/command/context/export.go +++ b/cli/command/context/export.go @@ -13,6 +13,8 @@ import ( ) // ExportOptions are the options used for exporting a context +// +// Deprecated: this type was for internal use and will be removed in the next release. type ExportOptions struct { ContextName string Dest string @@ -24,15 +26,14 @@ func newExportCommand(dockerCLI command.Cli) *cobra.Command { Short: "Export a context to a tar archive FILE or a tar stream on STDOUT.", Args: cli.RequiresRangeArgs(1, 2), RunE: func(cmd *cobra.Command, args []string) error { - opts := &ExportOptions{ - ContextName: args[0], - } + contextName := args[0] + var dest string if len(args) == 2 { - opts.Dest = args[1] + dest = args[1] } else { - opts.Dest = opts.ContextName + ".dockercontext" + dest = contextName + ".dockercontext" } - return RunExport(dockerCLI, opts) + return runExport(dockerCLI, contextName, dest) }, ValidArgsFunction: completeContextNames(dockerCLI, 1, true), } @@ -65,11 +66,21 @@ func writeTo(dockerCli command.Cli, reader io.Reader, dest string) error { } // RunExport exports a Docker context +// +// Deprecated: this function was for internal use and will be removed in the next release. func RunExport(dockerCli command.Cli, opts *ExportOptions) error { - if err := store.ValidateContextName(opts.ContextName); err != nil && opts.ContextName != command.DefaultContextName { + if opts == nil { + opts = &ExportOptions{} + } + return runExport(dockerCli, opts.ContextName, opts.Dest) +} + +// runExport exports a Docker context. +func runExport(dockerCLI command.Cli, contextName string, dest string) error { + if err := store.ValidateContextName(contextName); err != nil && contextName != command.DefaultContextName { return err } - reader := store.Export(opts.ContextName, dockerCli.ContextStore()) + reader := store.Export(contextName, dockerCLI.ContextStore()) defer reader.Close() - return writeTo(dockerCli, reader, opts.Dest) + return writeTo(dockerCLI, reader, dest) } diff --git a/cli/command/context/import.go b/cli/command/context/import.go index 182defcf53dc..aa7a728e1c78 100644 --- a/cli/command/context/import.go +++ b/cli/command/context/import.go @@ -18,7 +18,7 @@ func newImportCommand(dockerCli command.Cli) *cobra.Command { Short: "Import a context from a tar or zip file", Args: cli.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - return RunImport(dockerCli, args[0], args[1]) + return runImport(dockerCli, args[0], args[1]) }, // TODO(thaJeztah): this should also include "-" ValidArgsFunction: completion.FileNames, @@ -27,14 +27,21 @@ func newImportCommand(dockerCli command.Cli) *cobra.Command { } // RunImport imports a Docker context -func RunImport(dockerCli command.Cli, name string, source string) error { - if err := checkContextNameForCreation(dockerCli.ContextStore(), name); err != nil { +// +// Deprecated: this function was for internal use and will be removed in the next release. +func RunImport(dockerCLI command.Cli, name string, source string) error { + return runImport(dockerCLI, name, source) +} + +// runImport imports a Docker context. +func runImport(dockerCLI command.Cli, name string, source string) error { + if err := checkContextNameForCreation(dockerCLI.ContextStore(), name); err != nil { return err } var reader io.Reader if source == "-" { - reader = dockerCli.In() + reader = dockerCLI.In() } else { f, err := os.Open(source) if err != nil { @@ -44,11 +51,11 @@ func RunImport(dockerCli command.Cli, name string, source string) error { reader = f } - if err := store.Import(name, dockerCli.ContextStore(), reader); err != nil { + if err := store.Import(name, dockerCLI.ContextStore(), reader); err != nil { return err } - _, _ = fmt.Fprintln(dockerCli.Out(), name) - _, _ = fmt.Fprintf(dockerCli.Err(), "Successfully imported context %q\n", name) + _, _ = fmt.Fprintln(dockerCLI.Out(), name) + _, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully imported context %q\n", name) return nil } diff --git a/cli/command/context/list_test.go b/cli/command/context/list_test.go index ea7dd1e7c9e0..c98699b74f56 100644 --- a/cli/command/context/list_test.go +++ b/cli/command/context/list_test.go @@ -19,10 +19,10 @@ func createTestContexts(t *testing.T, cli command.Cli, name ...string) { func createTestContext(t *testing.T, cli command.Cli, name string, metaData map[string]any) { t.Helper() - err := RunCreate(cli, &CreateOptions{ - Name: name, - Description: "description of " + name, - Docker: map[string]string{keyHost: "https://someswarmserver.example.com"}, + err := runCreate(cli, &createOptions{ + name: name, + description: "description of " + name, + endpoint: map[string]string{keyHost: "https://someswarmserver.example.com"}, metaData: metaData, }) diff --git a/cli/command/context/remove.go b/cli/command/context/remove.go index 0f73cb1fd749..c3397e8b9c76 100644 --- a/cli/command/context/remove.go +++ b/cli/command/context/remove.go @@ -11,34 +11,48 @@ import ( ) // RemoveOptions are the options used to remove contexts +// +// Deprecated: this type was for internal use and will be removed in the next release. type RemoveOptions struct { Force bool } +// removeOptions are the options used to remove contexts. +type removeOptions struct { + force bool +} + func newRemoveCommand(dockerCLI command.Cli) *cobra.Command { - var opts RemoveOptions + var opts removeOptions cmd := &cobra.Command{ Use: "rm CONTEXT [CONTEXT...]", Aliases: []string{"remove"}, Short: "Remove one or more contexts", Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return RunRemove(dockerCLI, opts, args) + return runRemove(dockerCLI, opts, args) }, ValidArgsFunction: completeContextNames(dockerCLI, -1, false), } - cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Force the removal of a context in use") + cmd.Flags().BoolVarP(&opts.force, "force", "f", false, "Force the removal of a context in use") return cmd } // RunRemove removes one or more contexts -func RunRemove(dockerCLI command.Cli, opts RemoveOptions, names []string) error { +// +// Deprecated: this function was for internal use and will be removed in the next release. +func RunRemove(dockerCLI command.Cli, opts removeOptions, names []string) error { + return runRemove(dockerCLI, opts, names) +} + +// runRemove removes one or more contexts. +func runRemove(dockerCLI command.Cli, opts removeOptions, names []string) error { var errs []error currentCtx := dockerCLI.CurrentContext() for _, name := range names { if name == "default" { errs = append(errs, errors.New(`context "default" cannot be removed`)) - } else if err := doRemove(dockerCLI, name, name == currentCtx, opts.Force); err != nil { + } else if err := doRemove(dockerCLI, name, name == currentCtx, opts.force); err != nil { errs = append(errs, err) } else { _, _ = fmt.Fprintln(dockerCLI.Out(), name) diff --git a/cli/command/context/remove_test.go b/cli/command/context/remove_test.go index 9ebb944251d6..d121f39d6df8 100644 --- a/cli/command/context/remove_test.go +++ b/cli/command/context/remove_test.go @@ -14,7 +14,7 @@ import ( func TestRemove(t *testing.T) { cli := makeFakeCli(t) createTestContexts(t, cli, "current", "other") - assert.NilError(t, RunRemove(cli, RemoveOptions{}, []string{"other"})) + assert.NilError(t, runRemove(cli, removeOptions{}, []string{"other"})) _, err := cli.ContextStore().GetMetadata("current") assert.NilError(t, err) _, err = cli.ContextStore().GetMetadata("other") @@ -24,10 +24,10 @@ func TestRemove(t *testing.T) { func TestRemoveNotAContext(t *testing.T) { cli := makeFakeCli(t) createTestContexts(t, cli, "current", "other") - err := RunRemove(cli, RemoveOptions{}, []string{"not-a-context"}) + err := runRemove(cli, removeOptions{}, []string{"not-a-context"}) assert.ErrorContains(t, err, `context "not-a-context" does not exist`) - err = RunRemove(cli, RemoveOptions{Force: true}, []string{"not-a-context"}) + err = runRemove(cli, removeOptions{force: true}, []string{"not-a-context"}) assert.NilError(t, err) } @@ -35,7 +35,7 @@ func TestRemoveCurrent(t *testing.T) { cli := makeFakeCli(t) createTestContexts(t, cli, "current", "other") cli.SetCurrentContext("current") - err := RunRemove(cli, RemoveOptions{}, []string{"current"}) + err := runRemove(cli, removeOptions{}, []string{"current"}) assert.ErrorContains(t, err, `context "current" is in use, set -f flag to force remove`) } @@ -49,7 +49,7 @@ func TestRemoveCurrentForce(t *testing.T) { cli := makeFakeCli(t, withCliConfig(testCfg)) createTestContexts(t, cli, "current", "other") cli.SetCurrentContext("current") - assert.NilError(t, RunRemove(cli, RemoveOptions{Force: true}, []string{"current"})) + assert.NilError(t, runRemove(cli, removeOptions{force: true}, []string{"current"})) reloadedConfig, err := config.Load(configDir) assert.NilError(t, err) assert.Equal(t, "", reloadedConfig.CurrentContext) @@ -59,6 +59,6 @@ func TestRemoveDefault(t *testing.T) { cli := makeFakeCli(t) createTestContext(t, cli, "other", nil) cli.SetCurrentContext("current") - err := RunRemove(cli, RemoveOptions{}, []string{"default"}) + err := runRemove(cli, removeOptions{}, []string{"default"}) assert.ErrorContains(t, err, `context "default" cannot be removed`) } diff --git a/cli/command/context/update.go b/cli/command/context/update.go index 0995c52ef553..2c6a54eda03a 100644 --- a/cli/command/context/update.go +++ b/cli/command/context/update.go @@ -13,12 +13,21 @@ import ( ) // UpdateOptions are the options used to update a context +// +// Deprecated: this type was for internal use and will be removed in the next release. type UpdateOptions struct { Name string Description string Docker map[string]string } +// updateOptions are the options used to update a context. +type updateOptions struct { + name string + description string + endpoint map[string]string +} + func longUpdateDescription() string { buf := bytes.NewBuffer(nil) buf.WriteString("Update a context\n\nDocker endpoint config:\n\n") @@ -33,31 +42,45 @@ func longUpdateDescription() string { } func newUpdateCommand(dockerCLI command.Cli) *cobra.Command { - opts := &UpdateOptions{} + opts := updateOptions{} cmd := &cobra.Command{ Use: "update [OPTIONS] CONTEXT", Short: "Update a context", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.Name = args[0] - return RunUpdate(dockerCLI, opts) + opts.name = args[0] + return runUpdate(dockerCLI, &opts) }, Long: longUpdateDescription(), ValidArgsFunction: completeContextNames(dockerCLI, 1, false), } flags := cmd.Flags() - flags.StringVar(&opts.Description, "description", "", "Description of the context") - flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint") + flags.StringVar(&opts.description, "description", "", "Description of the context") + flags.StringToStringVar(&opts.endpoint, "docker", nil, "set the docker endpoint") return cmd } // RunUpdate updates a Docker context +// +// Deprecated: this function was for internal use and will be removed in the next release. func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error { - if err := store.ValidateContextName(o.Name); err != nil { + if o == nil { + o = &UpdateOptions{} + } + return runUpdate(dockerCLI, &updateOptions{ + name: o.Name, + description: o.Description, + endpoint: o.Docker, + }) +} + +// runUpdate updates a Docker context. +func runUpdate(dockerCLI command.Cli, opts *updateOptions) error { + if err := store.ValidateContextName(opts.name); err != nil { return err } s := dockerCLI.ContextStore() - c, err := s.GetMetadata(o.Name) + c, err := s.GetMetadata(opts.name) if err != nil { return err } @@ -65,16 +88,16 @@ func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error { if err != nil { return err } - if o.Description != "" { - dockerContext.Description = o.Description + if opts.description != "" { + dockerContext.Description = opts.description } c.Metadata = dockerContext tlsDataToReset := make(map[string]*store.EndpointTLSData) - if o.Docker != nil { - dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(s, o.Docker) + if opts.endpoint != nil { + dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(s, opts.endpoint) if err != nil { return fmt.Errorf("unable to create docker endpoint config: %w", err) } @@ -88,13 +111,13 @@ func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error { return err } for ep, tlsData := range tlsDataToReset { - if err := s.ResetEndpointTLSMaterial(o.Name, ep, tlsData); err != nil { + if err := s.ResetEndpointTLSMaterial(opts.name, ep, tlsData); err != nil { return err } } - _, _ = fmt.Fprintln(dockerCLI.Out(), o.Name) - _, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully updated context %q\n", o.Name) + _, _ = fmt.Fprintln(dockerCLI.Out(), opts.name) + _, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully updated context %q\n", opts.name) return nil } diff --git a/cli/command/context/update_test.go b/cli/command/context/update_test.go index 0e4a63914559..3d6493003139 100644 --- a/cli/command/context/update_test.go +++ b/cli/command/context/update_test.go @@ -11,16 +11,16 @@ import ( func TestUpdateDescriptionOnly(t *testing.T) { cli := makeFakeCli(t) - err := RunCreate(cli, &CreateOptions{ - Name: "test", - Docker: map[string]string{}, + err := runCreate(cli, &createOptions{ + name: "test", + endpoint: map[string]string{}, }) assert.NilError(t, err) cli.OutBuffer().Reset() cli.ErrBuffer().Reset() - assert.NilError(t, RunUpdate(cli, &UpdateOptions{ - Name: "test", - Description: "description", + assert.NilError(t, runUpdate(cli, &updateOptions{ + name: "test", + description: "description", })) c, err := cli.ContextStore().GetMetadata("test") assert.NilError(t, err) @@ -35,9 +35,9 @@ func TestUpdateDescriptionOnly(t *testing.T) { func TestUpdateDockerOnly(t *testing.T) { cli := makeFakeCli(t) createTestContext(t, cli, "test", nil) - assert.NilError(t, RunUpdate(cli, &UpdateOptions{ - Name: "test", - Docker: map[string]string{ + assert.NilError(t, runUpdate(cli, &updateOptions{ + name: "test", + endpoint: map[string]string{ keyHost: "tcp://some-host", }, })) @@ -52,14 +52,14 @@ func TestUpdateDockerOnly(t *testing.T) { func TestUpdateInvalidDockerHost(t *testing.T) { cli := makeFakeCli(t) - err := RunCreate(cli, &CreateOptions{ - Name: "test", - Docker: map[string]string{}, + err := runCreate(cli, &createOptions{ + name: "test", + endpoint: map[string]string{}, }) assert.NilError(t, err) - err = RunUpdate(cli, &UpdateOptions{ - Name: "test", - Docker: map[string]string{ + err = runUpdate(cli, &updateOptions{ + name: "test", + endpoint: map[string]string{ keyHost: "some///invalid/host", }, }) diff --git a/cli/command/context/use.go b/cli/command/context/use.go index 6b0d927a80af..2fad07eafd4f 100644 --- a/cli/command/context/use.go +++ b/cli/command/context/use.go @@ -17,7 +17,7 @@ func newUseCommand(dockerCLI command.Cli) *cobra.Command { Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { name := args[0] - return RunUse(dockerCLI, name) + return runUse(dockerCLI, name) }, ValidArgsFunction: completeContextNames(dockerCLI, 1, false), } @@ -25,7 +25,14 @@ func newUseCommand(dockerCLI command.Cli) *cobra.Command { } // RunUse set the current Docker context +// +// Deprecated: this function was for internal use and will be removed in the next release. func RunUse(dockerCLI command.Cli, name string) error { + return runUse(dockerCLI, name) +} + +// runUse set the current Docker context +func runUse(dockerCLI command.Cli, name string) error { // configValue uses an empty string for "default" var configValue string if name != command.DefaultContextName { diff --git a/cli/command/context/use_test.go b/cli/command/context/use_test.go index ebab2d5e7f5a..0e584b2e6e6c 100644 --- a/cli/command/context/use_test.go +++ b/cli/command/context/use_test.go @@ -23,9 +23,9 @@ func TestUse(t *testing.T) { configFilePath := filepath.Join(configDir, "config.json") testCfg := configfile.New(configFilePath) cli := makeFakeCli(t, withCliConfig(testCfg)) - err := RunCreate(cli, &CreateOptions{ - Name: "test", - Docker: map[string]string{}, + err := runCreate(cli, &createOptions{ + name: "test", + endpoint: map[string]string{}, }) assert.NilError(t, err) assert.NilError(t, newUseCommand(cli).RunE(nil, []string{"test"})) @@ -89,9 +89,9 @@ func TestUseHostOverride(t *testing.T) { configFilePath := filepath.Join(configDir, "config.json") testCfg := configfile.New(configFilePath) cli := makeFakeCli(t, withCliConfig(testCfg)) - err := RunCreate(cli, &CreateOptions{ - Name: "test", - Docker: map[string]string{}, + err := runCreate(cli, &createOptions{ + name: "test", + endpoint: map[string]string{}, }) assert.NilError(t, err) @@ -136,9 +136,9 @@ func TestUseHostOverrideEmpty(t *testing.T) { assert.NilError(t, cli.Initialize(flags.NewClientOptions())) } loadCli() - err := RunCreate(cli, &CreateOptions{ - Name: "test", - Docker: map[string]string{"host": socketPath}, + err := runCreate(cli, &createOptions{ + name: "test", + endpoint: map[string]string{"host": socketPath}, }) assert.NilError(t, err) From 139968dff212296b9331a200572c28b107b45af8 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sat, 30 Aug 2025 11:57:03 +0200 Subject: [PATCH 127/193] cli/command/completion: deprecate NoComplete This function was an exact duplicate of [cobra.NoFileCompletions], so deprecating it in favor of that. [cobra.NoFileCompletions]: https://pkg.go.dev/github.com/spf13/cobra@v1.9.1#NoFileCompletions Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 2827d037baee02889bc9a926755ddc6e39c00935) Signed-off-by: Sebastiaan van Stijn --- cli/command/builder/prune.go | 3 +-- cli/command/checkpoint/create.go | 3 +-- cli/command/completion/functions.go | 4 +++- cli/command/completion/functions_test.go | 6 ------ cli/command/config/create.go | 3 +-- cli/command/config/ls.go | 3 +-- cli/command/container/create.go | 2 +- cli/command/container/list.go | 3 +-- cli/command/container/prune.go | 3 +-- cli/command/container/run.go | 2 +- cli/command/context/create.go | 3 +-- cli/command/context/list.go | 3 +-- cli/command/context/show.go | 3 +-- cli/command/image/load.go | 2 +- cli/command/image/prune.go | 3 +-- cli/command/image/pull.go | 2 +- cli/command/network/create.go | 3 +-- cli/command/network/list.go | 3 +-- cli/command/node/list.go | 5 ++--- cli/command/node/ps.go | 3 +-- cli/command/node/update.go | 2 +- cli/command/plugin/create.go | 3 +-- cli/command/plugin/list.go | 3 +-- cli/command/registry/login.go | 3 +-- cli/command/service/create.go | 4 ++-- cli/command/service/inspect.go | 3 +-- cli/command/service/list.go | 5 ++--- cli/command/service/logs.go | 3 +-- cli/command/service/ps.go | 3 +-- cli/command/service/rollback.go | 3 +-- cli/command/service/update.go | 2 +- cli/command/stack/config.go | 3 +-- cli/command/stack/list.go | 3 +-- cli/command/swarm/ca.go | 3 +-- cli/command/swarm/init.go | 3 +-- cli/command/swarm/leave.go | 3 +-- cli/command/swarm/unlock.go | 3 +-- cli/command/swarm/unlock_key.go | 3 +-- cli/command/swarm/update.go | 3 +-- cli/command/system/df.go | 3 +-- cli/command/system/dial_stdio.go | 3 +-- cli/command/system/events.go | 3 +-- cli/command/system/info.go | 3 +-- cli/command/system/inspect.go | 4 ++-- cli/command/system/prune.go | 3 +-- cli/command/system/version.go | 3 +-- cli/command/volume/create.go | 3 +-- cli/command/volume/list.go | 3 +-- cli/command/volume/prune.go | 3 +-- 49 files changed, 54 insertions(+), 97 deletions(-) diff --git a/cli/command/builder/prune.go b/cli/command/builder/prune.go index 1c36f358edfa..9eef43216429 100644 --- a/cli/command/builder/prune.go +++ b/cli/command/builder/prune.go @@ -8,7 +8,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/internal/prompt" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/build" @@ -50,7 +49,7 @@ func newPruneCommand(dockerCLI command.Cli) *cobra.Command { return nil }, Annotations: map[string]string{"version": "1.39"}, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/checkpoint/create.go b/cli/command/checkpoint/create.go index 8455e979e64f..aa471bf3b83e 100644 --- a/cli/command/checkpoint/create.go +++ b/cli/command/checkpoint/create.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/docker/api/types/checkpoint" "github.com/spf13/cobra" ) @@ -30,7 +29,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command { opts.checkpoint = args[1] return runCreate(cmd.Context(), dockerCli, opts) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/completion/functions.go b/cli/command/completion/functions.go index b6387c127883..9a7d67b174ec 100644 --- a/cli/command/completion/functions.go +++ b/cli/command/completion/functions.go @@ -141,7 +141,9 @@ func FileNames(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCom return nil, cobra.ShellCompDirectiveDefault } -// NoComplete is used for commands where there's no relevant completion +// NoComplete is used for commands where there's no relevant completion. +// +// Deprecated: use [cobra.NoFileCompletions]. This function will be removed in the next release. func NoComplete(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveNoFileComp } diff --git a/cli/command/completion/functions_test.go b/cli/command/completion/functions_test.go index 12cd0ce425ee..2c2aa307fa1d 100644 --- a/cli/command/completion/functions_test.go +++ b/cli/command/completion/functions_test.go @@ -288,12 +288,6 @@ func TestCompleteNetworkNames(t *testing.T) { } } -func TestCompleteNoComplete(t *testing.T) { - values, directives := NoComplete(nil, nil, "") - assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp)) - assert.Check(t, is.Len(values, 0)) -} - func TestCompletePlatforms(t *testing.T) { values, directives := Platforms(nil, nil, "") assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") diff --git a/cli/command/config/create.go b/cli/command/config/create.go index 2bbe1cf61367..e6e2c39b073b 100644 --- a/cli/command/config/create.go +++ b/cli/command/config/create.go @@ -7,7 +7,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/swarm" "github.com/moby/sys/sequential" @@ -47,7 +46,7 @@ func newConfigCreateCommand(dockerCLI command.Cli) *cobra.Command { createOpts.file = args[1] return runCreate(cmd.Context(), dockerCLI, createOpts) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() flags.VarP(&createOpts.labels, "label", "l", "Config labels") diff --git a/cli/command/config/ls.go b/cli/command/config/ls.go index 63ffdae4de48..d7cb10f1c990 100644 --- a/cli/command/config/ls.go +++ b/cli/command/config/ls.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/opts" @@ -42,7 +41,7 @@ func newConfigListCommand(dockerCLI command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(cmd.Context(), dockerCLI, listOpts) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/container/create.go b/cli/command/container/create.go index de635606b8d7..f38746fd3cf6 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -105,7 +105,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd diff --git a/cli/command/container/list.go b/cli/command/container/list.go index 5a30291ff224..a76e1ef1468f 100644 --- a/cli/command/container/list.go +++ b/cli/command/container/list.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/opts" @@ -50,7 +49,7 @@ func newPsCommand(dockerCLI command.Cli) *cobra.Command { "category-top": "3", "aliases": "docker container ls, docker container list, docker container ps, docker ps", }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/container/prune.go b/cli/command/container/prune.go index 5baf040b79fa..2e0439ae070f 100644 --- a/cli/command/container/prune.go +++ b/cli/command/container/prune.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/internal/prompt" "github.com/docker/cli/opts" "github.com/docker/go-units" @@ -45,7 +44,7 @@ func newPruneCommand(dockerCLI command.Cli) *cobra.Command { return nil }, Annotations: map[string]string{"version": "1.25"}, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/container/run.go b/cli/command/container/run.go index 2674e5b11c4b..3cdb71c927d5 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -84,7 +84,7 @@ func newRunCommand(dockerCLI command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd diff --git a/cli/command/context/create.go b/cli/command/context/create.go index 308aa8720069..0e1c649c4aa7 100644 --- a/cli/command/context/create.go +++ b/cli/command/context/create.go @@ -11,7 +11,6 @@ import ( "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter/tabwriter" "github.com/docker/cli/cli/context/docker" "github.com/docker/cli/cli/context/store" @@ -54,7 +53,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command { return RunCreate(dockerCLI, opts) }, Long: longCreateDescription(), - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() flags.StringVar(&opts.Description, "description", "", "Description of the context") diff --git a/cli/command/context/list.go b/cli/command/context/list.go index b4258b91a302..88e080cacd27 100644 --- a/cli/command/context/list.go +++ b/cli/command/context/list.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/context/docker" flagsHelper "github.com/docker/cli/cli/flags" @@ -34,7 +33,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(dockerCli, opts) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/context/show.go b/cli/command/context/show.go index dd33f9e02a84..d5e4a828e35a 100644 --- a/cli/command/context/show.go +++ b/cli/command/context/show.go @@ -5,7 +5,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/spf13/cobra" ) @@ -19,7 +18,7 @@ func newShowCommand(dockerCli command.Cli) *cobra.Command { runShow(dockerCli) return nil }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } return cmd } diff --git a/cli/command/image/load.go b/cli/command/image/load.go index 268c18d2ef77..442178e54a4e 100644 --- a/cli/command/image/load.go +++ b/cli/command/image/load.go @@ -42,7 +42,7 @@ func newLoadCommand(dockerCLI command.Cli) *cobra.Command { Annotations: map[string]string{ "aliases": "docker image load, docker load", }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/image/prune.go b/cli/command/image/prune.go index f9db8761e17e..adc9773c30bd 100644 --- a/cli/command/image/prune.go +++ b/cli/command/image/prune.go @@ -8,7 +8,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/internal/prompt" "github.com/docker/cli/opts" "github.com/docker/go-units" @@ -49,7 +48,7 @@ func newPruneCommand(dockerCLI command.Cli) *cobra.Command { return nil }, Annotations: map[string]string{"version": "1.25"}, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index 99ac61951849..668c9cbc8bac 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -46,7 +46,7 @@ func newPullCommand(dockerCLI command.Cli) *cobra.Command { "category-top": "5", "aliases": "docker image pull, docker pull", }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/network/create.go b/cli/command/network/create.go index c8fca4129429..c19444f90def 100644 --- a/cli/command/network/create.go +++ b/cli/command/network/create.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" @@ -69,7 +68,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command { return runCreate(cmd.Context(), dockerCLI.Client(), dockerCLI.Out(), options) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/network/list.go b/cli/command/network/list.go index b4fd10a7dd43..9ac2367b6180 100644 --- a/cli/command/network/list.go +++ b/cli/command/network/list.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/opts" @@ -33,7 +32,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(cmd.Context(), dockerCli, options) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/node/list.go b/cli/command/node/list.go index 821c1b4f0f94..3364b2daa217 100644 --- a/cli/command/node/list.go +++ b/cli/command/node/list.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/opts" @@ -34,7 +33,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(cmd.Context(), dockerCli, options) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs") @@ -45,7 +44,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd } diff --git a/cli/command/node/ps.go b/cli/command/node/ps.go index 3552de76af1b..62b5ee141348 100644 --- a/cli/command/node/ps.go +++ b/cli/command/node/ps.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/idresolver" "github.com/docker/cli/cli/command/task" "github.com/docker/cli/opts" @@ -54,7 +53,7 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd } diff --git a/cli/command/node/update.go b/cli/command/node/update.go index 3d4bc11401d2..9ece2c9599ec 100644 --- a/cli/command/node/update.go +++ b/cli/command/node/update.go @@ -43,7 +43,7 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd } diff --git a/cli/command/plugin/create.go b/cli/command/plugin/create.go index b10da8cb29de..2b744c469582 100644 --- a/cli/command/plugin/create.go +++ b/cli/command/plugin/create.go @@ -10,7 +10,6 @@ import ( "github.com/distribution/reference" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/docker/api/types" "github.com/moby/go-archive" "github.com/moby/go-archive/compression" @@ -76,7 +75,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command { options.context = args[1] return runCreate(cmd.Context(), dockerCli, options) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/plugin/list.go b/cli/command/plugin/list.go index da08a06f955c..85521671465e 100644 --- a/cli/command/plugin/list.go +++ b/cli/command/plugin/list.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/opts" @@ -32,7 +31,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(cmd.Context(), dockerCli, options) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/registry/login.go b/cli/command/registry/login.go index 65e90d0cd679..a94a29e93a1b 100644 --- a/cli/command/registry/login.go +++ b/cli/command/registry/login.go @@ -11,7 +11,6 @@ import ( "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/config/configfile" configtypes "github.com/docker/cli/cli/config/types" "github.com/docker/cli/internal/oauth/manager" @@ -59,7 +58,7 @@ func newLoginCommand(dockerCLI command.Cli) *cobra.Command { Annotations: map[string]string{ "category-top": "8", }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/service/create.go b/cli/command/service/create.go index f63f65aefe24..621fc9f36feb 100644 --- a/cli/command/service/create.go +++ b/cli/command/service/create.go @@ -29,7 +29,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command { } return runCreate(cmd.Context(), dockerCLI, cmd.Flags(), opts) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() flags.StringVar(&opts.mode, flagMode, "replicated", `Service mode ("replicated", "global", "replicated-job", "global-job")`) @@ -94,7 +94,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd } diff --git a/cli/command/service/inspect.go b/cli/command/service/inspect.go index d94aeceb9c21..aa8cbcbcf667 100644 --- a/cli/command/service/inspect.go +++ b/cli/command/service/inspect.go @@ -10,7 +10,6 @@ import ( "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/docker/api/types/network" @@ -52,7 +51,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd } diff --git a/cli/command/service/list.go b/cli/command/service/list.go index d2768cc0e197..278a7dcc7239 100644 --- a/cli/command/service/list.go +++ b/cli/command/service/list.go @@ -5,7 +5,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/opts" @@ -33,7 +32,7 @@ func newListCommand(dockerCLI command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(cmd.Context(), dockerCLI, options) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() @@ -45,7 +44,7 @@ func newListCommand(dockerCLI command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd } diff --git a/cli/command/service/logs.go b/cli/command/service/logs.go index a3e29f6b454c..b92f8dcf0fba 100644 --- a/cli/command/service/logs.go +++ b/cli/command/service/logs.go @@ -12,7 +12,6 @@ import ( "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/idresolver" "github.com/docker/cli/internal/logdetails" @@ -73,7 +72,7 @@ func newLogsCommand(dockerCli command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd } diff --git a/cli/command/service/ps.go b/cli/command/service/ps.go index 601014054d0d..b55db793d29b 100644 --- a/cli/command/service/ps.go +++ b/cli/command/service/ps.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/idresolver" "github.com/docker/cli/cli/command/node" "github.com/docker/cli/cli/command/task" @@ -52,7 +51,7 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd } diff --git a/cli/command/service/rollback.go b/cli/command/service/rollback.go index 3b4a445f2e23..a100c9f0d9fa 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/docker/cli/cli/command/completion" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/versions" "github.com/spf13/cobra" @@ -35,7 +34,7 @@ func newRollbackCommand(dockerCli command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd } diff --git a/cli/command/service/update.go b/cli/command/service/update.go index 5f9971adf287..7ebcb461b55c 100644 --- a/cli/command/service/update.go +++ b/cli/command/service/update.go @@ -137,7 +137,7 @@ func newUpdateCommand(dockerCLI command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd diff --git a/cli/command/stack/config.go b/cli/command/stack/config.go index 045db77b5fa6..9af8acf10338 100644 --- a/cli/command/stack/config.go +++ b/cli/command/stack/config.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/stack/loader" "github.com/docker/cli/cli/command/stack/options" composeLoader "github.com/docker/cli/cli/compose/loader" @@ -36,7 +35,7 @@ func newConfigCommand(dockerCli command.Cli) *cobra.Command { _, err = fmt.Fprintf(dockerCli.Out(), "%s", cfg) return err }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index 1b4831d43a9e..74a7fd98f8b0 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -7,7 +7,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/cli/command/stack/swarm" @@ -29,7 +28,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(cmd.Context(), dockerCli, opts) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/swarm/ca.go b/cli/command/swarm/ca.go index edde543f5a57..7155f7e11e1f 100644 --- a/cli/command/swarm/ca.go +++ b/cli/command/swarm/ca.go @@ -8,7 +8,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/swarm/progress" "github.com/docker/cli/internal/jsonstream" "github.com/docker/docker/api/types/swarm" @@ -40,7 +39,7 @@ func newCACommand(dockerCli command.Cli) *cobra.Command { "version": "1.30", "swarm": "manager", }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/swarm/init.go b/cli/command/swarm/init.go index d61aab755bd0..f779787a02d9 100644 --- a/cli/command/swarm/init.go +++ b/cli/command/swarm/init.go @@ -8,7 +8,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -44,7 +43,7 @@ func newInitCommand(dockerCli command.Cli) *cobra.Command { "version": "1.24", "swarm": "", // swarm init does not require swarm to be active, and is always available on API 1.24 and up }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/swarm/leave.go b/cli/command/swarm/leave.go index 28c8d603aa60..335dbb0042e5 100644 --- a/cli/command/swarm/leave.go +++ b/cli/command/swarm/leave.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/spf13/cobra" ) @@ -28,7 +27,7 @@ func newLeaveCommand(dockerCli command.Cli) *cobra.Command { "version": "1.24", "swarm": "active", }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/swarm/unlock.go b/cli/command/swarm/unlock.go index e2af459b83b1..8f5980b00932 100644 --- a/cli/command/swarm/unlock.go +++ b/cli/command/swarm/unlock.go @@ -9,7 +9,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/streams" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -29,7 +28,7 @@ func newUnlockCommand(dockerCli command.Cli) *cobra.Command { "version": "1.24", "swarm": "manager", }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } return cmd diff --git a/cli/command/swarm/unlock_key.go b/cli/command/swarm/unlock_key.go index af62e0b09a95..a8cd82c5938c 100644 --- a/cli/command/swarm/unlock_key.go +++ b/cli/command/swarm/unlock_key.go @@ -7,7 +7,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -32,7 +31,7 @@ func newUnlockKeyCommand(dockerCli command.Cli) *cobra.Command { "version": "1.24", "swarm": "manager", }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/swarm/update.go b/cli/command/swarm/update.go index c356e994a067..f90041d39cc5 100644 --- a/cli/command/swarm/update.go +++ b/cli/command/swarm/update.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -33,7 +32,7 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command { "version": "1.24", "swarm": "manager", }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } cmd.Flags().BoolVar(&opts.autolock, flagAutolock, false, "Change manager autolocking setting (true|false)") diff --git a/cli/command/system/df.go b/cli/command/system/df.go index f6a98a036776..cb040049fa53 100644 --- a/cli/command/system/df.go +++ b/cli/command/system/df.go @@ -5,7 +5,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/docker/api/types" @@ -29,7 +28,7 @@ func newDiskUsageCommand(dockerCli command.Cli) *cobra.Command { return runDiskUsage(cmd.Context(), dockerCli, opts) }, Annotations: map[string]string{"version": "1.25"}, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/system/dial_stdio.go b/cli/command/system/dial_stdio.go index c8ccd181c794..fc1cbee27a73 100644 --- a/cli/command/system/dial_stdio.go +++ b/cli/command/system/dial_stdio.go @@ -7,7 +7,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -23,7 +22,7 @@ func newDialStdioCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runDialStdio(cmd.Context(), dockerCli) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } return cmd } diff --git a/cli/command/system/events.go b/cli/command/system/events.go index 782f9eff5224..f8d2e69fe11a 100644 --- a/cli/command/system/events.go +++ b/cli/command/system/events.go @@ -11,7 +11,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/opts" @@ -48,7 +47,7 @@ func newEventsCommand(dockerCLI command.Cli) *cobra.Command { Annotations: map[string]string{ "aliases": "docker system events, docker events", }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/system/info.go b/cli/command/system/info.go index 350431c8386c..b4b6daab6ffd 100644 --- a/cli/command/system/info.go +++ b/cli/command/system/info.go @@ -14,7 +14,6 @@ import ( "github.com/docker/cli/cli" pluginmanager "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/debug" flagsHelper "github.com/docker/cli/cli/flags" @@ -81,7 +80,7 @@ func newInfoCommand(dockerCLI command.Cli) *cobra.Command { "category-top": "12", "aliases": "docker system info, docker info", }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) diff --git a/cli/command/system/inspect.go b/cli/command/system/inspect.go index aa34169b9f3d..ef79831cd9af 100644 --- a/cli/command/system/inspect.go +++ b/cli/command/system/inspect.go @@ -82,7 +82,7 @@ func newInspectCommand(dockerCLI command.Cli) *cobra.Command { return runInspect(cmd.Context(), dockerCLI, opts) }, // TODO(thaJeztah): should we consider adding completion for common object-types? (images, containers?) - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() @@ -95,7 +95,7 @@ func newInspectCommand(dockerCLI command.Cli) *cobra.Command { // Set a default completion function if none was set. We don't look // up if it does already have one set, because Cobra does this for // us, and returns an error (which we ignore for this reason). - _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + _ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions) }) return cmd } diff --git a/cli/command/system/prune.go b/cli/command/system/prune.go index a1bcb5f2241b..a5ddde4a4a0e 100644 --- a/cli/command/system/prune.go +++ b/cli/command/system/prune.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/builder" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/container" "github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/command/network" @@ -45,7 +44,7 @@ func newPruneCommand(dockerCli command.Cli) *cobra.Command { return runPrune(cmd.Context(), dockerCli, options) }, Annotations: map[string]string{"version": "1.25"}, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/system/version.go b/cli/command/system/version.go index a1839ff64135..3cf8683d81ca 100644 --- a/cli/command/system/version.go +++ b/cli/command/system/version.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/formatter/tabwriter" flagsHelper "github.com/docker/cli/cli/flags" @@ -129,7 +128,7 @@ func newVersionCommand(dockerCLI command.Cli) *cobra.Command { Annotations: map[string]string{ "category-top": "10", }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) diff --git a/cli/command/volume/create.go b/cli/command/volume/create.go index 8c13f65a74eb..06679f75c566 100644 --- a/cli/command/volume/create.go +++ b/cli/command/volume/create.go @@ -9,7 +9,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/volume" "github.com/spf13/cobra" @@ -59,7 +58,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command { options.cluster = hasClusterVolumeOptionSet(cmd.Flags()) return runCreate(cmd.Context(), dockerCli, options) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() flags.StringVarP(&options.driver, "driver", "d", "local", "Specify volume driver name") diff --git a/cli/command/volume/list.go b/cli/command/volume/list.go index 297c90b7c987..4286da5b7561 100644 --- a/cli/command/volume/list.go +++ b/cli/command/volume/list.go @@ -6,7 +6,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/opts" @@ -37,7 +36,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(cmd.Context(), dockerCli, options) }, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() diff --git a/cli/command/volume/prune.go b/cli/command/volume/prune.go index 1f90292d94a6..d8e1aa7508bc 100644 --- a/cli/command/volume/prune.go +++ b/cli/command/volume/prune.go @@ -7,7 +7,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/internal/prompt" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/versions" @@ -48,7 +47,7 @@ func newPruneCommand(dockerCLI command.Cli) *cobra.Command { return nil }, Annotations: map[string]string{"version": "1.25"}, - ValidArgsFunction: completion.NoComplete, + ValidArgsFunction: cobra.NoFileCompletions, } flags := cmd.Flags() From 2b9489d827bd2345bad893f4a04065d53f240acd Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 1 Sep 2025 11:23:26 +0200 Subject: [PATCH 128/193] [28.x] vendor: github.com/docker/docker v28.4.0-rc.1 full diff: https://github.com/moby/moby/compare/02b4a1a3decc...v28.4.0-rc.1 Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 2 +- vendor.sum | 4 +- .../github.com/docker/docker/api/swagger.yaml | 2 + .../api/types/container/network_settings.go | 53 ++++++++++++++----- .../docker/api/types/types_deprecated.go | 7 +-- vendor/modules.txt | 2 +- 6 files changed, 51 insertions(+), 19 deletions(-) diff --git a/vendor.mod b/vendor.mod index 3a6bf2ef0bba..4cf695ef0ca6 100644 --- a/vendor.mod +++ b/vendor.mod @@ -16,7 +16,7 @@ require ( github.com/distribution/reference v0.6.0 github.com/docker/cli-docs-tool v0.10.0 github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.3.4-0.20250828134822-02b4a1a3decc+incompatible // 28.x branch (v28.4.0-dev) + github.com/docker/docker v28.4.0-rc.1+incompatible // 28.x branch (v28.4.0-dev) github.com/docker/docker-credential-helpers v0.9.3 github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 diff --git a/vendor.sum b/vendor.sum index 7ab4efcdc5f6..c818b0105a4b 100644 --- a/vendor.sum +++ b/vendor.sum @@ -57,8 +57,8 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.3.4-0.20250828134822-02b4a1a3decc+incompatible h1:a4+xSvQFFYl8eRsVWyL3lwz1JubDEO77m1ovj3lFiXo= -github.com/docker/docker v28.3.4-0.20250828134822-02b4a1a3decc+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.4.0-rc.1+incompatible h1:gPjgKl1UvGNcEMOgzhMFkcUJGjJohj5/GxOrOITtJlA= +github.com/docker/docker v28.4.0-rc.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= diff --git a/vendor/github.com/docker/docker/api/swagger.yaml b/vendor/github.com/docker/docker/api/swagger.yaml index 16da2ce49195..1401fa715359 100644 --- a/vendor/github.com/docker/docker/api/swagger.yaml +++ b/vendor/github.com/docker/docker/api/swagger.yaml @@ -1608,6 +1608,8 @@ definitions: Bridge: description: | Name of the default bridge interface when dockerd's --bridge flag is set. + + Deprecated: This field is only set when the daemon is started with the --bridge flag specified. type: "string" example: "docker0" SandboxID: diff --git a/vendor/github.com/docker/docker/api/types/container/network_settings.go b/vendor/github.com/docker/docker/api/types/container/network_settings.go index afec0e54323e..687145f2953a 100644 --- a/vendor/github.com/docker/docker/api/types/container/network_settings.go +++ b/vendor/github.com/docker/docker/api/types/container/network_settings.go @@ -13,8 +13,11 @@ type NetworkSettings struct { } // NetworkSettingsBase holds networking state for a container when inspecting it. +// +// Deprecated: Most fields in NetworkSettingsBase are deprecated. Fields which aren't deprecated will move to +// NetworkSettings in v29.0, and this struct will be removed. type NetworkSettingsBase struct { - Bridge string // Bridge contains the name of the default bridge interface iff it was set through the daemon --bridge flag. + Bridge string // Deprecated: This field is only set when the daemon is started with the --bridge flag specified. SandboxID string // SandboxID uniquely represents a container's network stack SandboxKey string // SandboxKey identifies the sandbox Ports nat.PortMap // Ports is a collection of PortBinding indexed by Port @@ -35,18 +38,44 @@ type NetworkSettingsBase struct { SecondaryIPv6Addresses []network.Address // Deprecated: This field is never set and will be removed in a future release. } -// DefaultNetworkSettings holds network information -// during the 2 release deprecation period. -// It will be removed in Docker 1.11. +// DefaultNetworkSettings holds the networking state for the default bridge, if the container is connected to that +// network. +// +// Deprecated: this struct is deprecated since Docker v1.11 and will be removed in v29. You should look for the default +// network in NetworkSettings.Networks instead. type DefaultNetworkSettings struct { - EndpointID string // EndpointID uniquely represents a service endpoint in a Sandbox - Gateway string // Gateway holds the gateway address for the network - GlobalIPv6Address string // GlobalIPv6Address holds network's global IPv6 address - GlobalIPv6PrefixLen int // GlobalIPv6PrefixLen represents mask length of network's global IPv6 address - IPAddress string // IPAddress holds the IPv4 address for the network - IPPrefixLen int // IPPrefixLen represents mask length of network's IPv4 address - IPv6Gateway string // IPv6Gateway holds gateway address specific for IPv6 - MacAddress string // MacAddress holds the MAC address for the network + // EndpointID uniquely represents a service endpoint in a Sandbox + // + // Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead. + EndpointID string + // Gateway holds the gateway address for the network + // + // Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead. + Gateway string + // GlobalIPv6Address holds network's global IPv6 address + // + // Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead. + GlobalIPv6Address string + // GlobalIPv6PrefixLen represents mask length of network's global IPv6 address + // + // Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead. + GlobalIPv6PrefixLen int + // IPAddress holds the IPv4 address for the network + // + // Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead. + IPAddress string + // IPPrefixLen represents mask length of network's IPv4 address + // + // Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead. + IPPrefixLen int + // IPv6Gateway holds gateway address specific for IPv6 + // + // Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead. + IPv6Gateway string + // MacAddress holds the MAC address for the network + // + // Deprecated: This field will be removed in v29. You should look for the default network in NetworkSettings.Networks instead. + MacAddress string } // NetworkSettingsSummary provides a summary of container's networks diff --git a/vendor/github.com/docker/docker/api/types/types_deprecated.go b/vendor/github.com/docker/docker/api/types/types_deprecated.go index 8456a45607e2..c9c20b873604 100644 --- a/vendor/github.com/docker/docker/api/types/types_deprecated.go +++ b/vendor/github.com/docker/docker/api/types/types_deprecated.go @@ -46,15 +46,16 @@ type NetworkSettings = container.NetworkSettings // NetworkSettingsBase holds networking state for a container when inspecting it. // -// Deprecated: use [container.NetworkSettingsBase]. -type NetworkSettingsBase = container.NetworkSettingsBase +// Deprecated: [container.NetworkSettingsBase] will be removed in v29. Prefer +// accessing the fields it contains through [container.NetworkSettings]. +type NetworkSettingsBase = container.NetworkSettingsBase //nolint:staticcheck // ignore SA1019: NetworkSettingsBase is deprecated in v28.4. // DefaultNetworkSettings holds network information // during the 2 release deprecation period. // It will be removed in Docker 1.11. // // Deprecated: use [container.DefaultNetworkSettings]. -type DefaultNetworkSettings = container.DefaultNetworkSettings +type DefaultNetworkSettings = container.DefaultNetworkSettings //nolint:staticcheck // ignore SA1019: DefaultNetworkSettings is deprecated in v28.4. // SummaryNetworkSettings provides a summary of container's networks // in /containers/json. diff --git a/vendor/modules.txt b/vendor/modules.txt index a98f67d7e4c0..813f2c6848f6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/uuid -# github.com/docker/docker v28.3.4-0.20250828134822-02b4a1a3decc+incompatible +# github.com/docker/docker v28.4.0-rc.1+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types From 8e49313c0c4d398ca0535309f6ca9e3cad1c6374 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 1 Sep 2025 17:33:32 +0200 Subject: [PATCH 129/193] cli-plugins/manager: deprecate ReexecEnvvar This alias was added in 4321293972a4ed804e8c063868cc5da6147ce73b, which is part of v28.0, but did not deprecate them. They are no longer used in the CLI itself, but may be used by cli-plugin implementations. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 6fa7d183204046300b972ee4cb8a121e17770e86) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/manager.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli-plugins/manager/manager.go b/cli-plugins/manager/manager.go index b76dbbe37667..bab031811761 100644 --- a/cli-plugins/manager/manager.go +++ b/cli-plugins/manager/manager.go @@ -23,6 +23,8 @@ const ( // used to originally invoke the docker CLI when executing a // plugin. Assuming $PATH and $CWD remain unchanged this should allow // the plugin to re-execute the original CLI. + // + // Deprecated: use [metadata.ReexecEnvvar]. This alias will be removed in the next release. ReexecEnvvar = metadata.ReexecEnvvar ) From 991d942cc36c25b15711e4f6bf3532d788b6cddb Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 1 Sep 2025 17:43:39 +0200 Subject: [PATCH 130/193] cli/command/registry: deprecate OauthLoginEscapeHatchEnvVar This const was added in 846ecf59ffb555d615cf78fe4e4c43e71c9697b8, but only used internally. This patch deprecates the const, to be removed in the next release. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 18cdc25bb4675ff9555f221524572f97b706ab8b) Signed-off-by: Sebastiaan van Stijn --- cli/command/registry/login.go | 7 ++++++- cli/command/registry/login_test.go | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cli/command/registry/login.go b/cli/command/registry/login.go index a94a29e93a1b..09e997c80090 100644 --- a/cli/command/registry/login.go +++ b/cli/command/registry/login.go @@ -184,10 +184,15 @@ func loginWithStoredCredentials(ctx context.Context, dockerCLI command.Cli, auth return response.Status, err } +// OauthLoginEscapeHatchEnvVar disables the browser-based OAuth login workflow. +// +// Deprecated: this const was only used internally and will be removed in the next release. const OauthLoginEscapeHatchEnvVar = "DOCKER_CLI_DISABLE_OAUTH_LOGIN" +const oauthLoginEscapeHatchEnvVar = "DOCKER_CLI_DISABLE_OAUTH_LOGIN" + func isOauthLoginDisabled() bool { - if v := os.Getenv(OauthLoginEscapeHatchEnvVar); v != "" { + if v := os.Getenv(oauthLoginEscapeHatchEnvVar); v != "" { enabled, err := strconv.ParseBool(v) if err != nil { return false diff --git a/cli/command/registry/login_test.go b/cli/command/registry/login_test.go index 7e6ca2ec1c4f..756da7d74f3c 100644 --- a/cli/command/registry/login_test.go +++ b/cli/command/registry/login_test.go @@ -533,7 +533,7 @@ func TestIsOauthLoginDisabled(t *testing.T) { } for _, tc := range testCases { - t.Setenv(OauthLoginEscapeHatchEnvVar, tc.envVar) + t.Setenv(oauthLoginEscapeHatchEnvVar, tc.envVar) disabled := isOauthLoginDisabled() From 9deb5e9cd307492548776923f0b9e8be04bce567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Tue, 2 Sep 2025 12:15:13 +0200 Subject: [PATCH 131/193] vendor: github.com/docker/docker v28.4.0-rc.2-dev (5d5332b00c76) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit full diff: https://github.com/docker/docker/compare/v28.4.0-rc.1...5d5332b00c76 Signed-off-by: Paweł Gronowski --- vendor.mod | 2 +- vendor.sum | 4 ++-- vendor/modules.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vendor.mod b/vendor.mod index 4cf695ef0ca6..592dedd18d82 100644 --- a/vendor.mod +++ b/vendor.mod @@ -16,7 +16,7 @@ require ( github.com/distribution/reference v0.6.0 github.com/docker/cli-docs-tool v0.10.0 github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.4.0-rc.1+incompatible // 28.x branch (v28.4.0-dev) + github.com/docker/docker v28.4.0-rc.1.0.20250902095642-5d5332b00c76+incompatible // 28.x branch (v28.4.0-dev) github.com/docker/docker-credential-helpers v0.9.3 github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 diff --git a/vendor.sum b/vendor.sum index c818b0105a4b..7c5384390a47 100644 --- a/vendor.sum +++ b/vendor.sum @@ -57,8 +57,8 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.4.0-rc.1+incompatible h1:gPjgKl1UvGNcEMOgzhMFkcUJGjJohj5/GxOrOITtJlA= -github.com/docker/docker v28.4.0-rc.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.4.0-rc.1.0.20250902095642-5d5332b00c76+incompatible h1:tl2qgu+4b/D7S63nPqQa4234kklUdz8EhAxJ5RPYBlE= +github.com/docker/docker v28.4.0-rc.1.0.20250902095642-5d5332b00c76+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= diff --git a/vendor/modules.txt b/vendor/modules.txt index 813f2c6848f6..c7995145ad60 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/uuid -# github.com/docker/docker v28.4.0-rc.1+incompatible +# github.com/docker/docker v28.4.0-rc.1.0.20250902095642-5d5332b00c76+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types From b3df92053dd94fb66999010986b62dff335c71d0 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 3 Sep 2025 15:39:41 +0200 Subject: [PATCH 132/193] add completion for docker image pull With this patch, completion is provided for images already present in the local image cache to help pulling the latest version of the same tag; docker pull go golang:1.12 golang:1.18.0 golang:1.21 golang:1.24 gopher:latest golang:1.13 golang:1.20 golang:1.23 golang:latest docker pull golang: 1.12 1.13 1.18.0 1.20 1.21 1.23 1.24 latest Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 5bf3c6793df77aeb9aaa564ef9f2907ae3ef6aad) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/pull.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index 668c9cbc8bac..f1d66e2e8261 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -46,7 +46,9 @@ func newPullCommand(dockerCLI command.Cli) *cobra.Command { "category-top": "5", "aliases": "docker image pull, docker pull", }, - ValidArgsFunction: cobra.NoFileCompletions, + // Complete with local images to help pulling the latest version + // of images that are in the image cache. + ValidArgsFunction: completion.ImageNames(dockerCLI, 1), } flags := cmd.Flags() From 6e20b9d93c07e827bceb772b5e4ae4d09830d985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Wed, 3 Sep 2025 20:40:59 +0200 Subject: [PATCH 133/193] update to go1.24.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This includes 1 security fix: - net/http: CrossOriginProtection bypass patterns are over-broad When passing patterns to CrossOriginProtection.AddInsecureBypassPattern, requests that would have redirected to those patterns (e.g. without a trailing slash) were also exempted, which might be unexpected. Thanks to Marco Gazerro for reporting this issue. This is CVE-2025-47910 and Go issue https://go.dev/issue/75054. View the release notes for more information: https://go.dev/doc/devel/release#go1.24.7 Signed-off-by: Paweł Gronowski (cherry picked from commit f64b8a332dc25438e7e6f891706b19a0e4659c6e) Signed-off-by: Paweł Gronowski --- .github/workflows/codeql.yml | 2 +- .github/workflows/test.yml | 2 +- .golangci.yml | 2 +- Dockerfile | 2 +- docker-bake.hcl | 2 +- dockerfiles/Dockerfile.dev | 2 +- dockerfiles/Dockerfile.lint | 2 +- dockerfiles/Dockerfile.vendor | 2 +- e2e/testdata/Dockerfile.gencerts | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 161db5196cee..d2f86bdffda7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -63,7 +63,7 @@ jobs: name: Update Go uses: actions/setup-go@v5 with: - go-version: "1.24.6" + go-version: "1.24.7" - name: Initialize CodeQL uses: github/codeql-action/init@v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 31113fb7886d..527fca878417 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,7 +66,7 @@ jobs: name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.24.6" + go-version: "1.24.7" - name: Test run: | diff --git a/.golangci.yml b/.golangci.yml index 1365f9fa3f2b..01273f2d28a1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,7 @@ run: # which causes it to fallback to go1.17 semantics. # # TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24 - go: "1.24.6" + go: "1.24.7" timeout: 5m diff --git a/Dockerfile b/Dockerfile index a29d9d5a7a1e..aef1a3c73669 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ARG BASE_VARIANT=alpine ARG ALPINE_VERSION=3.21 ARG BASE_DEBIAN_DISTRO=bookworm -ARG GO_VERSION=1.24.6 +ARG GO_VERSION=1.24.7 ARG XX_VERSION=1.6.1 ARG GOVERSIONINFO_VERSION=v1.4.1 diff --git a/docker-bake.hcl b/docker-bake.hcl index e486e2e037a9..99c62722c536 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,5 +1,5 @@ variable "GO_VERSION" { - default = "1.24.6" + default = "1.24.7" } variable "VERSION" { default = "" diff --git a/dockerfiles/Dockerfile.dev b/dockerfiles/Dockerfile.dev index 46107d78b27e..9446d249277d 100644 --- a/dockerfiles/Dockerfile.dev +++ b/dockerfiles/Dockerfile.dev @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.6 +ARG GO_VERSION=1.24.7 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/dockerfiles/Dockerfile.lint b/dockerfiles/Dockerfile.lint index 517badda71c1..d5babf5e19b7 100644 --- a/dockerfiles/Dockerfile.lint +++ b/dockerfiles/Dockerfile.lint @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.6 +ARG GO_VERSION=1.24.7 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/dockerfiles/Dockerfile.vendor b/dockerfiles/Dockerfile.vendor index aa329107ae51..633a9e88721c 100644 --- a/dockerfiles/Dockerfile.vendor +++ b/dockerfiles/Dockerfile.vendor @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.6 +ARG GO_VERSION=1.24.7 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/e2e/testdata/Dockerfile.gencerts b/e2e/testdata/Dockerfile.gencerts index fbb0c4b30941..2063c2bc0a78 100644 --- a/e2e/testdata/Dockerfile.gencerts +++ b/e2e/testdata/Dockerfile.gencerts @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.6 +ARG GO_VERSION=1.24.7 FROM golang:${GO_VERSION}-alpine AS generated ENV GOTOOLCHAIN=local From 18adfd54a946aa638bea1cc2688a209974d1c375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Wed, 3 Sep 2025 20:58:26 +0200 Subject: [PATCH 134/193] vendor: github.com/docker/docker v28.4.0-rc.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit full diff: https://github.com/docker/docker/compare/5d5332b00c76...v28.4.0-rc.2 Signed-off-by: Paweł Gronowski --- vendor.mod | 2 +- vendor.sum | 4 ++-- vendor/modules.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vendor.mod b/vendor.mod index 592dedd18d82..6fcfa0361be9 100644 --- a/vendor.mod +++ b/vendor.mod @@ -16,7 +16,7 @@ require ( github.com/distribution/reference v0.6.0 github.com/docker/cli-docs-tool v0.10.0 github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.4.0-rc.1.0.20250902095642-5d5332b00c76+incompatible // 28.x branch (v28.4.0-dev) + github.com/docker/docker v28.4.0-rc.2+incompatible github.com/docker/docker-credential-helpers v0.9.3 github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 diff --git a/vendor.sum b/vendor.sum index 7c5384390a47..bb40328c6bb8 100644 --- a/vendor.sum +++ b/vendor.sum @@ -57,8 +57,8 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.4.0-rc.1.0.20250902095642-5d5332b00c76+incompatible h1:tl2qgu+4b/D7S63nPqQa4234kklUdz8EhAxJ5RPYBlE= -github.com/docker/docker v28.4.0-rc.1.0.20250902095642-5d5332b00c76+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.4.0-rc.2+incompatible h1:2ZReH+ZQw00136QxGbIZEG3Ycqy5nilPgZYuo4hnsRg= +github.com/docker/docker v28.4.0-rc.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= diff --git a/vendor/modules.txt b/vendor/modules.txt index c7995145ad60..c287e5b71ae5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/uuid -# github.com/docker/docker v28.4.0-rc.1.0.20250902095642-5d5332b00c76+incompatible +# github.com/docker/docker v28.4.0-rc.2+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types From a83e3df40b6a685f91a28e35c1692d97d0203763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Wed, 3 Sep 2025 22:32:04 +0200 Subject: [PATCH 135/193] vendor: github.com/docker/docker v28.4.0-dev (249d679a6baf) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit full diff: https://github.com/docker/docker/compare/v28.4.0-rc.2...249d679a6baf Signed-off-by: Paweł Gronowski --- vendor.mod | 2 +- vendor.sum | 4 ++-- vendor/modules.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vendor.mod b/vendor.mod index 6fcfa0361be9..c0c307b4a8bf 100644 --- a/vendor.mod +++ b/vendor.mod @@ -16,7 +16,7 @@ require ( github.com/distribution/reference v0.6.0 github.com/docker/cli-docs-tool v0.10.0 github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.4.0-rc.2+incompatible + github.com/docker/docker v28.4.0-rc.2.0.20250903202849-249d679a6baf+incompatible // 28.4.0-dev github.com/docker/docker-credential-helpers v0.9.3 github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 diff --git a/vendor.sum b/vendor.sum index bb40328c6bb8..2da56f22b022 100644 --- a/vendor.sum +++ b/vendor.sum @@ -57,8 +57,8 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.4.0-rc.2+incompatible h1:2ZReH+ZQw00136QxGbIZEG3Ycqy5nilPgZYuo4hnsRg= -github.com/docker/docker v28.4.0-rc.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.4.0-rc.2.0.20250903202849-249d679a6baf+incompatible h1:Pkl6clGEMTFjRD57vOF36IXOeqaURCc7OOz5iAiRY3U= +github.com/docker/docker v28.4.0-rc.2.0.20250903202849-249d679a6baf+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= diff --git a/vendor/modules.txt b/vendor/modules.txt index c287e5b71ae5..2d4412e631ca 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/uuid -# github.com/docker/docker v28.4.0-rc.2+incompatible +# github.com/docker/docker v28.4.0-rc.2.0.20250903202849-249d679a6baf+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types From b627f18262a806df5dea3b875e25fb48900958b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Thu, 4 Sep 2025 00:22:49 +0200 Subject: [PATCH 136/193] vendor: github.com/docker/docker v28.4.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit full diff: https://github.com/docker/docker/compare/249d679a6baf...v28.4.0 Signed-off-by: Paweł Gronowski --- vendor.mod | 2 +- vendor.sum | 4 ++-- vendor/modules.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vendor.mod b/vendor.mod index c0c307b4a8bf..b9beacdf0cdc 100644 --- a/vendor.mod +++ b/vendor.mod @@ -16,7 +16,7 @@ require ( github.com/distribution/reference v0.6.0 github.com/docker/cli-docs-tool v0.10.0 github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.4.0-rc.2.0.20250903202849-249d679a6baf+incompatible // 28.4.0-dev + github.com/docker/docker v28.4.0+incompatible github.com/docker/docker-credential-helpers v0.9.3 github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 diff --git a/vendor.sum b/vendor.sum index 2da56f22b022..23f314205ca4 100644 --- a/vendor.sum +++ b/vendor.sum @@ -57,8 +57,8 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.4.0-rc.2.0.20250903202849-249d679a6baf+incompatible h1:Pkl6clGEMTFjRD57vOF36IXOeqaURCc7OOz5iAiRY3U= -github.com/docker/docker v28.4.0-rc.2.0.20250903202849-249d679a6baf+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= +github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= diff --git a/vendor/modules.txt b/vendor/modules.txt index 2d4412e631ca..77e1dfc939a4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/uuid -# github.com/docker/docker v28.4.0-rc.2.0.20250903202849-249d679a6baf+incompatible +# github.com/docker/docker v28.4.0+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types From 2de1ea9769bc5832d2608f589859b6839a4265bd Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 4 Sep 2025 18:53:06 +0200 Subject: [PATCH 137/193] cli/context/docker: don't wrap client options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We may still change this, but in the client module, the signature of the client.Opt changed to now include a non-exported type, which means that we can't construct a custom option that is implemented using client options: #18 16.94 # github.com/docker/cli/cli/context/docker #18 16.94 cli/context/docker/load.go:105:29: cannot use withHTTPClient(tlsConfig) (value of type func(*client.Client) error) as client.Opt value in argument to append #18 16.94 cli/context/docker/load.go:152:6: cannot use c (variable of type *client.Client) as *client.clientConfig value in argument to client.WithHTTPClient(&http.Client{…}) We can consider exporting the `client.clientConfig` type (but keep its fields non-exported), but for this use, we don't strictly need it, so let's change the implementation to not having to depend on that. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit b0b0e457f0555f41229d392975e4969c02b0f3c4) Signed-off-by: Sebastiaan van Stijn --- cli/context/docker/load.go | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/cli/context/docker/load.go b/cli/context/docker/load.go index 89d43e2e3265..e37ee4646db6 100644 --- a/cli/context/docker/load.go +++ b/cli/context/docker/load.go @@ -101,7 +101,22 @@ func (ep *Endpoint) ClientOpts() ([]client.Opt, error) { if err != nil { return nil, err } - result = append(result, withHTTPClient(tlsConfig)) + + // If there's no tlsConfig available, we use the default HTTPClient. + if tlsConfig != nil { + result = append(result, + client.WithHTTPClient(&http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + DialContext: (&net.Dialer{ + KeepAlive: 30 * time.Second, + Timeout: 30 * time.Second, + }).DialContext, + }, + CheckRedirect: client.CheckRedirect, + }), + ) + } } result = append(result, client.WithHost(ep.Host)) } else { @@ -133,25 +148,6 @@ func isSocket(addr string) bool { } } -func withHTTPClient(tlsConfig *tls.Config) func(*client.Client) error { - return func(c *client.Client) error { - if tlsConfig == nil { - // Use the default HTTPClient - return nil - } - return client.WithHTTPClient(&http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - DialContext: (&net.Dialer{ - KeepAlive: 30 * time.Second, - Timeout: 30 * time.Second, - }).DialContext, - }, - CheckRedirect: client.CheckRedirect, - })(c) - } -} - // EndpointFromContext parses a context docker endpoint metadata into a typed EndpointMeta structure func EndpointFromContext(metadata store.Metadata) (EndpointMeta, error) { ep, ok := metadata.Endpoints[DockerEndpoint] From aa976192cbf8dfdad53f46943f3cffe7ba1e51a3 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 4 Sep 2025 18:57:07 +0200 Subject: [PATCH 138/193] cli/command: don't wrap client options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We may still change this, but in the client module, the signature of the client.Opt changed to now include a non-exported type, which means that we can't construct a custom option that is implemented using client options: #18 16.94 # github.com/docker/cli/cli/context/docker #18 16.94 cli/context/docker/load.go:105:29: cannot use withHTTPClient(tlsConfig) (value of type func(*client.Client) error) as client.Opt value in argument to append #18 16.94 cli/context/docker/load.go:152:6: cannot use c (variable of type *client.Client) as *client.clientConfig value in argument to client.WithHTTPClient(&http.Client{…}) We can consider exporting the `client.clientConfig` type (but keep its fields non-exported), but for this use, we don't strictly need it, so let's change the implementation to not having to depend on that. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit e7d14d905e5a9da4af84400217bda44fdaae3936) Signed-off-by: Sebastiaan van Stijn --- cli/command/cli.go | 9 +++- cli/command/cli_options.go | 96 +++++++++++++++++++------------------- 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/cli/command/cli.go b/cli/command/cli.go index 85fac565024b..b657ff313d49 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -324,7 +324,14 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF if len(configFile.HTTPHeaders) > 0 { opts = append(opts, client.WithHTTPHeaders(configFile.HTTPHeaders)) } - opts = append(opts, withCustomHeadersFromEnv(), client.WithUserAgent(UserAgent())) + withCustomHeaders, err := withCustomHeadersFromEnv() + if err != nil { + return nil, err + } + if withCustomHeaders != nil { + opts = append(opts, withCustomHeaders) + } + opts = append(opts, client.WithUserAgent(UserAgent())) return client.NewClientWithOpts(opts...) } diff --git a/cli/command/cli_options.go b/cli/command/cli_options.go index dd3c9473369d..a8bdd88eb486 100644 --- a/cli/command/cli_options.go +++ b/cli/command/cli_options.go @@ -180,61 +180,59 @@ const envOverrideHTTPHeaders = "DOCKER_CUSTOM_HEADERS" // override headers with the same name). // // TODO(thaJeztah): this is a client Option, and should be moved to the client. It is non-exported for that reason. -func withCustomHeadersFromEnv() client.Opt { - return func(apiClient *client.Client) error { - value := os.Getenv(envOverrideHTTPHeaders) - if value == "" { - return nil - } - csvReader := csv.NewReader(strings.NewReader(value)) - fields, err := csvReader.Read() - if err != nil { - return invalidParameter(errors.Errorf( - "failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs", - envOverrideHTTPHeaders, - )) - } - if len(fields) == 0 { - return nil - } - - env := map[string]string{} - for _, kv := range fields { - k, v, hasValue := strings.Cut(kv, "=") - - // Only strip whitespace in keys; preserve whitespace in values. - k = strings.TrimSpace(k) +func withCustomHeadersFromEnv() (client.Opt, error) { + value := os.Getenv(envOverrideHTTPHeaders) + if value == "" { + return nil, nil + } + csvReader := csv.NewReader(strings.NewReader(value)) + fields, err := csvReader.Read() + if err != nil { + return nil, invalidParameter(errors.Errorf( + "failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs", + envOverrideHTTPHeaders, + )) + } + if len(fields) == 0 { + return nil, nil + } - if k == "" { - return invalidParameter(errors.Errorf( - `failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`, - envOverrideHTTPHeaders, kv, - )) - } + env := map[string]string{} + for _, kv := range fields { + k, v, hasValue := strings.Cut(kv, "=") - // We don't currently allow empty key=value pairs, and produce an error. - // This is something we could allow in future (e.g. to read value - // from an environment variable with the same name). In the meantime, - // produce an error to prevent users from depending on this. - if !hasValue { - return invalidParameter(errors.Errorf( - `failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`, - envOverrideHTTPHeaders, kv, - )) - } + // Only strip whitespace in keys; preserve whitespace in values. + k = strings.TrimSpace(k) - env[http.CanonicalHeaderKey(k)] = v + if k == "" { + return nil, invalidParameter(errors.Errorf( + `failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`, + envOverrideHTTPHeaders, kv, + )) } - if len(env) == 0 { - // We should probably not hit this case, as we don't skip values - // (only return errors), but we don't want to discard existing - // headers with an empty set. - return nil + // We don't currently allow empty key=value pairs, and produce an error. + // This is something we could allow in future (e.g. to read value + // from an environment variable with the same name). In the meantime, + // produce an error to prevent users from depending on this. + if !hasValue { + return nil, invalidParameter(errors.Errorf( + `failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`, + envOverrideHTTPHeaders, kv, + )) } - // TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_ - // see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc) - return client.WithHTTPHeaders(env)(apiClient) + env[http.CanonicalHeaderKey(k)] = v + } + + if len(env) == 0 { + // We should probably not hit this case, as we don't skip values + // (only return errors), but we don't want to discard existing + // headers with an empty set. + return nil, nil } + + // TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_ + // see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc) + return client.WithHTTPHeaders(env), nil } From cab5014d577c1f1eae2f511362b3790075696869 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 28 Jul 2025 08:17:24 +0200 Subject: [PATCH 139/193] templates: deprecate NewParse() It it just a chain of `New("sometag").Parse(...)`, and most of our uses don't use a tag for the template, so can call Parse. There's no public users of this function, but deprecating it first just in case. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 7ab3e7e77469ca5fe57bfa88e6f0a5b0f302ea53) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/list.go | 2 +- cli/command/system/info.go | 2 +- cli/command/system/version.go | 3 +-- templates/templates.go | 6 ++++-- templates/templates_test.go | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cli/command/container/list.go b/cli/command/container/list.go index a76e1ef1468f..06e3a73d4ec7 100644 --- a/cli/command/container/list.go +++ b/cli/command/container/list.go @@ -87,7 +87,7 @@ func buildContainerListOptions(options *psOptions) (*container.ListOptions, erro // always validate template when `--format` is used, for consistency if len(options.format) > 0 { - tmpl, err := templates.NewParse("", options.format) + tmpl, err := templates.Parse(options.format) if err != nil { return nil, errors.Wrap(err, "failed to parse template") } diff --git a/cli/command/system/info.go b/cli/command/system/info.go index b4b6daab6ffd..780d9b4dd0b8 100644 --- a/cli/command/system/info.go +++ b/cli/command/system/info.go @@ -168,7 +168,7 @@ func needsServerInfo(template string, info dockerInfo) bool { } // A template is provided and has at least one field set. - tmpl, err := templates.NewParse("", template) + tmpl, err := templates.Parse(template) if err != nil { // ignore parsing errors here, and let regular code handle them return true diff --git a/cli/command/system/version.go b/cli/command/system/version.go index 3cf8683d81ca..b9d57618b3e4 100644 --- a/cli/command/system/version.go +++ b/cli/command/system/version.go @@ -215,8 +215,7 @@ func newVersionTemplate(templateFormat string) (*template.Template, error) { case formatter.JSONFormatKey: templateFormat = formatter.JSONFormat } - tmpl := templates.New("version").Funcs(template.FuncMap{"getDetailsOrder": getDetailsOrder}) - tmpl, err := tmpl.Parse(templateFormat) + tmpl, err := templates.New("version").Funcs(template.FuncMap{"getDetailsOrder": getDetailsOrder}).Parse(templateFormat) if err != nil { return nil, errors.Wrap(err, "template parsing error") } diff --git a/templates/templates.go b/templates/templates.go index f0726eec95c5..4af4496d19a1 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -71,7 +71,7 @@ var HeaderFunctions = template.FuncMap{ // Parse creates a new anonymous template with the basic functions // and parses the given format. func Parse(format string) (*template.Template, error) { - return NewParse("", format) + return template.New("").Funcs(basicFunctions).Parse(format) } // New creates a new empty template with the provided tag and built-in @@ -82,8 +82,10 @@ func New(tag string) *template.Template { // NewParse creates a new tagged template with the basic functions // and parses the given format. +// +// Deprecated: this function is unused and will be removed in the next release. Use [New] if you need to set a tag, or [Parse] instead. func NewParse(tag, format string) (*template.Template, error) { - return New(tag).Parse(format) + return template.New(tag).Funcs(basicFunctions).Parse(format) } // padWithSpace adds whitespace to the input if the input is non-empty diff --git a/templates/templates_test.go b/templates/templates_test.go index 608fe72a2004..e9dbaefd0e5e 100644 --- a/templates/templates_test.go +++ b/templates/templates_test.go @@ -30,7 +30,7 @@ func TestParseStringFunctions(t *testing.T) { } func TestNewParse(t *testing.T) { - tm, err := NewParse("foo", "this is a {{ . }}") + tm, err := New("foo").Parse("this is a {{ . }}") assert.NilError(t, err) var b bytes.Buffer From c1914f73f53989dcdeed817fddc1741407ab7039 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 11:32:37 +0000 Subject: [PATCH 140/193] build(deps): bump actions/setup-go from 5 to 6 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] (cherry picked from commit 44e66a97a9066d964d2a85a8eb1d93c701a0470d) Signed-off-by: Sebastiaan van Stijn --- .github/workflows/codeql.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d2f86bdffda7..ce4e9b81f8cf 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -61,7 +61,7 @@ jobs: ln -s vendor.sum go.sum - name: Update Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: "1.24.7" - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 527fca878417..4c36cebbd26b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,7 +64,7 @@ jobs: path: ${{ env.GOPATH }}/src/github.com/docker/cli - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: "1.24.7" - From 428518a84a4a366a0c05193e16b91a10cd1df664 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 24 Sep 2025 10:12:05 +0200 Subject: [PATCH 141/193] gha: update test-matrix: remove docker 23.x, 26.x, add 25.x - Mirantis Container Runtime (MCR) 23.0 reached EOL, and the next LTS version of MCR is 25.x - Docker 26.x reached EOL and is no longer maintained Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 83e40c39b49926c6b76b7bcda9cf6420c10bcf30) Signed-off-by: Sebastiaan van Stijn --- .github/workflows/e2e.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 7ecdb039c15c..1ba37f0318bc 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -39,8 +39,7 @@ jobs: engine-version: - 28 # latest - 27 # latest - 1 - - 26 # github actions default - - 23 # mirantis lts + - 25 # mirantis lts steps: - name: Checkout From a0ebf3e35ca844aa265be81cdc0502d502a42b9b Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 11 Sep 2025 13:58:50 +0200 Subject: [PATCH 142/193] cli/command/container: improve TestContainerStatsContext - Don't use unnamed keys - Use sub-tests - Add test-cases for Name and ID fields Signed-off-by: Sebastiaan van Stijn (cherry picked from commit b8cda96d117d8c2eb639869d373f3cff359a8bc0) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/formatter_stats_test.go | 146 +++++++++++++++--- 1 file changed, 124 insertions(+), 22 deletions(-) diff --git a/cli/command/container/formatter_stats_test.go b/cli/command/container/formatter_stats_test.go index 91c69a5d7c26..220dc53d0aed 100644 --- a/cli/command/container/formatter_stats_test.go +++ b/cli/command/container/formatter_stats_test.go @@ -14,36 +14,138 @@ func TestContainerStatsContext(t *testing.T) { containerID := test.RandomID() var ctx statsContext - tt := []struct { + tests := []struct { + name string stats StatsEntry osType string expValue string expHeader string call func() string }{ - {StatsEntry{Container: containerID}, "", containerID, containerHeader, ctx.Container}, - {StatsEntry{CPUPercentage: 5.5}, "", "5.50%", cpuPercHeader, ctx.CPUPerc}, - {StatsEntry{CPUPercentage: 5.5, IsInvalid: true}, "", "--", cpuPercHeader, ctx.CPUPerc}, - {StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3}, "", "0.31B / 12.3B", netIOHeader, ctx.NetIO}, - {StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, "", "--", netIOHeader, ctx.NetIO}, - {StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, "", "0.1B / 2.3B", blockIOHeader, ctx.BlockIO}, - {StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true}, "", "--", blockIOHeader, ctx.BlockIO}, - {StatsEntry{MemoryPercentage: 10.2}, "", "10.20%", memPercHeader, ctx.MemPerc}, - {StatsEntry{MemoryPercentage: 10.2, IsInvalid: true}, "", "--", memPercHeader, ctx.MemPerc}, - {StatsEntry{MemoryPercentage: 10.2}, "windows", "--", memPercHeader, ctx.MemPerc}, - {StatsEntry{Memory: 24, MemoryLimit: 30}, "", "24B / 30B", memUseHeader, ctx.MemUsage}, - {StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, "", "-- / --", memUseHeader, ctx.MemUsage}, - {StatsEntry{Memory: 24, MemoryLimit: 30}, "windows", "24B", winMemUseHeader, ctx.MemUsage}, - {StatsEntry{PidsCurrent: 10}, "", "10", pidsHeader, ctx.PIDs}, - {StatsEntry{PidsCurrent: 10, IsInvalid: true}, "", "--", pidsHeader, ctx.PIDs}, - {StatsEntry{PidsCurrent: 10}, "windows", "--", pidsHeader, ctx.PIDs}, + { + name: "Container", + stats: StatsEntry{Container: containerID}, + expValue: containerID, + expHeader: containerHeader, + call: ctx.Container, + }, + { + name: "CPUPerc", + stats: StatsEntry{CPUPercentage: 5.5}, + expValue: "5.50%", + expHeader: cpuPercHeader, + call: ctx.CPUPerc, + }, + { + name: "CPUPerc invalid", + stats: StatsEntry{CPUPercentage: 5.5, IsInvalid: true}, + expValue: "--", + expHeader: cpuPercHeader, + call: ctx.CPUPerc, + }, + { + name: "NetIO", + stats: StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3}, + expValue: "0.31B / 12.3B", + expHeader: netIOHeader, + call: ctx.NetIO, + }, + { + name: "NetIO invalid", + stats: StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, + expValue: "--", + expHeader: netIOHeader, + call: ctx.NetIO, + }, + { + name: "BlockIO", + stats: StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, + expValue: "0.1B / 2.3B", + expHeader: blockIOHeader, + call: ctx.BlockIO, + }, + { + name: "BlockIO invalid", + stats: StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true}, + expValue: "--", + expHeader: blockIOHeader, + call: ctx.BlockIO, + }, + { + name: "MemPerc", + stats: StatsEntry{MemoryPercentage: 10.2}, + expValue: "10.20%", + expHeader: memPercHeader, + call: ctx.MemPerc, + }, + { + name: "MemPerc invalid", + stats: StatsEntry{MemoryPercentage: 10.2, IsInvalid: true}, + expValue: "--", + expHeader: memPercHeader, + call: ctx.MemPerc, + }, + { + name: "MemPerc windows", + stats: StatsEntry{MemoryPercentage: 10.2}, + osType: "windows", + expValue: "--", + expHeader: memPercHeader, + call: ctx.MemPerc, + }, + { + name: "MemUsage", + stats: StatsEntry{Memory: 24, MemoryLimit: 30}, + expValue: "24B / 30B", + expHeader: memUseHeader, + call: ctx.MemUsage, + }, + { + name: "MemUsage invalid", + stats: StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, + expValue: "-- / --", + expHeader: memUseHeader, + call: ctx.MemUsage, + }, + { + name: "MemUsage windows", + stats: StatsEntry{Memory: 24, MemoryLimit: 30}, + osType: "windows", + expValue: "24B", + expHeader: winMemUseHeader, + call: ctx.MemUsage, + }, + { + name: "PIDs", + stats: StatsEntry{PidsCurrent: 10}, + expValue: "10", + expHeader: pidsHeader, + call: ctx.PIDs, + }, + { + name: "PIDs invalid", + stats: StatsEntry{PidsCurrent: 10, IsInvalid: true}, + expValue: "--", + expHeader: pidsHeader, + call: ctx.PIDs, + }, + { + name: "PIDs windows", + stats: StatsEntry{PidsCurrent: 10}, + osType: "windows", + expValue: "--", + expHeader: pidsHeader, + call: ctx.PIDs, + }, } - for _, te := range tt { - ctx = statsContext{s: te.stats, os: te.osType} - if v := te.call(); v != te.expValue { - t.Fatalf("Expected %q, got %q", te.expValue, v) - } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx = statsContext{s: tc.stats, os: tc.osType} + if v := tc.call(); v != tc.expValue { + t.Fatalf("Expected %q, got %q", tc.expValue, v) + } + }) } } From 2fb1298416c9779f012a861abeac9db14eab2397 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 11 Sep 2025 14:10:54 +0200 Subject: [PATCH 143/193] cli/command/container: improve TestContainerStatsContext - Use sub-tests - Don't use un-named keys - Add test-cases for 'Name', 'ID' and custom container names Signed-off-by: Sebastiaan van Stijn (cherry picked from commit b9314938b742e799a3d3e127ef440db0011d9d25) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/formatter_stats_test.go | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/cli/command/container/formatter_stats_test.go b/cli/command/container/formatter_stats_test.go index 220dc53d0aed..59a7d70429a1 100644 --- a/cli/command/container/formatter_stats_test.go +++ b/cli/command/container/formatter_stats_test.go @@ -5,13 +5,12 @@ import ( "testing" "github.com/docker/cli/cli/command/formatter" - "github.com/docker/cli/internal/test" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) func TestContainerStatsContext(t *testing.T) { - containerID := test.RandomID() + const actorID = "c74518277ddc15a6afeaaeb06ee5f7433dcb27188224777c1efa7df1e8766d65" var ctx statsContext tests := []struct { @@ -23,12 +22,47 @@ func TestContainerStatsContext(t *testing.T) { call func() string }{ { - name: "Container", - stats: StatsEntry{Container: containerID}, - expValue: containerID, + name: "Container id", + stats: StatsEntry{ID: actorID, Container: actorID}, + expValue: actorID, expHeader: containerHeader, call: ctx.Container, }, + { + name: "Container name", + stats: StatsEntry{ID: actorID, Container: "a-long-container-name"}, + expValue: "a-long-container-name", + expHeader: containerHeader, + call: ctx.Container, + }, + { + name: "ID", + stats: StatsEntry{ID: actorID}, + expValue: actorID, + expHeader: formatter.ContainerIDHeader, + call: ctx.ID, + }, + { + name: "Name", + stats: StatsEntry{Name: "/container-name"}, + expValue: "container-name", + expHeader: formatter.ContainerIDHeader, + call: ctx.Name, + }, + { + name: "Name empty", + stats: StatsEntry{Name: ""}, + expValue: "--", + expHeader: formatter.ContainerIDHeader, + call: ctx.Name, + }, + { + name: "Name prefix only", + stats: StatsEntry{Name: "/"}, + expValue: "--", + expHeader: formatter.ContainerIDHeader, + call: ctx.Name, + }, { name: "CPUPerc", stats: StatsEntry{CPUPercentage: 5.5}, From 1cf78c49ab73b88a9690bb4f4eba808cc4abeb19 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 11 Sep 2025 13:00:53 +0200 Subject: [PATCH 144/193] cli/command/container: prevent panic during stats on empty event Actor.ID This code was missing a check for the ID field before truncating it to a shorter length for presentation. This would result in a panic if an event would either have an empty ID field or a shorter length ID; panic: runtime error: slice bounds out of range [:12] with length 0 goroutine 82 [running]: github.com/docker/cli/cli/command/container.RunStats.func2({{0x0, 0x0}, {0x0, 0x0}, {0x0, 0x0}, {0x40001fcba0, 0x9}, {0x40001fcba9, 0x5}, ...}) /go/src/github.com/docker/cli/cli/command/container/stats.go:146 +0x1d0 created by github.com/docker/cli/cli/command/container.(*eventHandler).watch in goroutine 6 /go/src/github.com/docker/cli/cli/command/container/stats.go:363 +0x1c8 We need to look at this code in general; the truncated ID is passed to NewStats, which uses the ID to propagate the `Container` field in the `StatsEntry` struct. which is not used in the default format used by `docker stats` and, having the same content as the `ID` field on the same struct, doesn't make it very useful, other than being able to present it under a `CONTAINER` column (instead of `CONTAINER ID`); we should consider deprecating it; there may be some subtle things to look into here; the `Container` field originally held the container name. This was changed in [moby@ef915fd], which introduced separate `ID` and `Name` fields, renaming the old `Name` field to container. Looking at [`Stats.SetStatistics()`] and related code in [stats_helpers.go], the `Container` field is used as the "canonical" reference for the stats record; this allows the stats _data_ to be refreshed when a new stats sample arrives for the same container (also see [moby@929a77b], which moved locking to the `Stats` wrapper struct). This construct allows to account for intermediate states, where a stats sample was incomplete or could produce an error; in that case, the reference to the container for which the stats were sampled is kept to allow removing a container from the list once the container was removed. We should consider removing `Container` as a formatting option, and moving the `Container` field to the outer struct; this makes the outer struct responsible for keeping a reference to the container, allowing the `StatsEntry` as a whole to be replaced atomically. This patch only addresses the panic; - It changes the logic to preserve the container ID verbatim instead of truncating. This allows stats samples to be matched against the `Actor.ID` as-is. - Truncating the `Container` is moved to the presentation logic; currently this does not take `--no-trunc` into account to keep the existing behavior, but we can (should) consider adding this. - Logging is improved to use structured logs, and an extra check is added to prevent empty IDs from being added as watcher. [`Stats.SetStatistics()`]: https://github.com/docker/cli/blob/82281087e3e186c5a2eafa0d973e849ff84c357d/cli/command/container/formatter_stats.go#L88-L94 [moby@ef915fd]: https://github.com/moby/moby/commit/ef915fd036d9ea5263f9370dce490ef97ea0618d [moby@929a77b]: https://github.com/moby/moby/commit/929a77b814dfe9ab7a11bffc2d16eebd27bd903a [stats_helpers.go]: https://github.com/docker/cli/blob/82281087e3e186c5a2eafa0d973e849ff84c357d/cli/command/container/stats_helpers.go#L26-L51 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 9b79e486464d4d505a9f81772a415a30b06016ba) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/formatter_stats.go | 1 + cli/command/container/stats.go | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cli/command/container/formatter_stats.go b/cli/command/container/formatter_stats.go index 48371eedc890..626c63142d91 100644 --- a/cli/command/container/formatter_stats.go +++ b/cli/command/container/formatter_stats.go @@ -167,6 +167,7 @@ func (c *statsContext) Container() string { } func (c *statsContext) Name() string { + // TODO(thaJeztah): make this explicitly trim the "/" prefix, not just any char. if len(c.s.Name) > 1 { return c.s.Name[1:] } diff --git a/cli/command/container/stats.go b/cli/command/container/stats.go index 7e3a94625296..f8ff880ca7ed 100644 --- a/cli/command/container/stats.go +++ b/cli/command/container/stats.go @@ -139,7 +139,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions) eh := newEventHandler() if options.All { eh.setHandler(events.ActionCreate, func(e events.Message) { - s := NewStats(e.Actor.ID[:12]) + s := NewStats(e.Actor.ID) if cStats.add(s) { waitFirst.Add(1) go collect(ctx, s, apiClient, !options.NoStream, waitFirst) @@ -148,7 +148,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions) } eh.setHandler(events.ActionStart, func(e events.Message) { - s := NewStats(e.Actor.ID[:12]) + s := NewStats(e.Actor.ID) if cStats.add(s) { waitFirst.Add(1) go collect(ctx, s, apiClient, !options.NoStream, waitFirst) @@ -157,7 +157,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions) if !options.All { eh.setHandler(events.ActionDie, func(e events.Message) { - cStats.remove(e.Actor.ID[:12]) + cStats.remove(e.Actor.ID) }) } @@ -210,7 +210,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions) return err } for _, ctr := range cs { - s := NewStats(ctr.ID[:12]) + s := NewStats(ctr.ID) if cStats.add(s) { waitFirst.Add(1) go collect(ctx, s, apiClient, !options.NoStream, waitFirst) @@ -363,7 +363,12 @@ func (eh *eventHandler) watch(c <-chan events.Message) { if !exists { continue } - logrus.Debugf("event handler: received event: %v", e) + if e.Actor.ID == "" { + logrus.WithField("event", e).Errorf("event handler: received %s event with empty ID", e.Action) + continue + } + + logrus.WithField("event", e).Debugf("event handler: received %s event for: %s", e.Action, e.Actor.ID) go h(e) } } From 4a677b86a6fd0689c9ca65c66c463b92b9535388 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 26 Aug 2025 23:47:17 +0200 Subject: [PATCH 145/193] cli/command/image: remove special handling for plugin errors on pull This special handling was added in [moby@9b6dcc8], and later updated in [moby@c127d96], but it fully depended on string-matching, which is brittle. Testing the original ticket that lead to this handling, it looks like the string matching no longer works, and the daemon error is returned as-is: With graphdrivers: docker pull tiborvass/no-remove Using default tag: latest Error response from daemon: Encountered remote "application/vnd.docker.plugin.v0+json"(unknown) when fetching With containerd snapshotters enabled: docker pull tiborvass/no-remove Using default tag: latest latest: Pulling from tiborvass/no-remove cf635291f7c9: Download complete failed to unpack image on snapshotter overlayfs: mismatched image rootfs and manifest layers The error-message for containerd can probably be improved, but as the special handling in the CLI no longer works, we can remove it. [moby@9b6dcc8]: https://github.com/moby/moby/commit/9b6dcc8b9d1366d3da3c8f60f89de1a36b087b88 [moby@c127d96]: https://github.com/moby/moby/commit/c127d9614f5b30bd73861877f8540a63e7d869e9 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 323fbc485e5902fbbab97ad86cb5d4f90f956e39) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/pull.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index f1d66e2e8261..2f010fb6f139 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -2,15 +2,14 @@ package image import ( "context" + "errors" "fmt" - "strings" "github.com/distribution/reference" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/trust" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -87,15 +86,13 @@ func runPull(ctx context.Context, dockerCLI command.Cli, opts pullOptions) error // Check if reference has a digest _, isCanonical := distributionRef.(reference.Canonical) if !opts.untrusted && !isCanonical { - err = trustedPull(ctx, dockerCLI, imgRefAndAuth, opts) + if err := trustedPull(ctx, dockerCLI, imgRefAndAuth, opts); err != nil { + return err + } } else { - err = imagePullPrivileged(ctx, dockerCLI, imgRefAndAuth, opts) - } - if err != nil { - if strings.Contains(err.Error(), "when fetching 'plugin'") { - return errors.New(err.Error() + " - Use `docker plugin install`") + if err := imagePullPrivileged(ctx, dockerCLI, imgRefAndAuth, opts); err != nil { + return err } - return err } _, _ = fmt.Fprintln(dockerCLI.Out(), imgRefAndAuth.Reference().String()) return nil From f77defa891ce4aacce3dc157da694e481a1213f1 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 8 Sep 2025 20:02:40 +0200 Subject: [PATCH 146/193] cli/command/plugin: remove special error handling on install, upgrade Similar to 323fbc485e5902fbbab97ad86cb5d4f90f956e39 - this code was added in [moby@c127d96], but used string-matching to detect cases where a user tried to install an image as plugin. However, this handling no longer matched any error-strings, so no longer worked: docker plugin install busybox Error response from daemon: did not find plugin config for specified reference docker.io/library/busybox:latest [moby@c127d96]: https://github.com/moby/moby/commit/c127d9614f5b30bd73861877f8540a63e7d869e9 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit fb3f2da50ea63e219080c2e63b64c64ac58c80be) Signed-off-by: Sebastiaan van Stijn --- cli/command/plugin/install.go | 4 ---- cli/command/plugin/install_test.go | 8 -------- cli/command/plugin/upgrade.go | 4 ---- 3 files changed, 16 deletions(-) diff --git a/cli/command/plugin/install.go b/cli/command/plugin/install.go index e232fdf0034a..715ae76ae107 100644 --- a/cli/command/plugin/install.go +++ b/cli/command/plugin/install.go @@ -3,7 +3,6 @@ package plugin import ( "context" "fmt" - "strings" "github.com/distribution/reference" "github.com/docker/cli/cli" @@ -120,9 +119,6 @@ func runInstall(ctx context.Context, dockerCLI command.Cli, opts pluginOptions) } responseBody, err := dockerCLI.Client().PluginInstall(ctx, localName, options) if err != nil { - if strings.Contains(err.Error(), "(image) when fetching") { - return errors.New(err.Error() + " - Use \"docker image pull\"") - } return err } defer func() { diff --git a/cli/command/plugin/install_test.go b/cli/command/plugin/install_test.go index f66082d9302e..ee67e611d7dd 100644 --- a/cli/command/plugin/install_test.go +++ b/cli/command/plugin/install_test.go @@ -43,14 +43,6 @@ func TestInstallErrors(t *testing.T) { return nil, errors.New("error installing plugin") }, }, - { - description: "installation error due to missing image", - args: []string{"foo"}, - expectedError: "docker image pull", - installFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) { - return nil, errors.New("(image) when fetching") - }, - }, } for _, tc := range testCases { diff --git a/cli/command/plugin/upgrade.go b/cli/command/plugin/upgrade.go index 7d4548d428f1..54a39685b702 100644 --- a/cli/command/plugin/upgrade.go +++ b/cli/command/plugin/upgrade.go @@ -3,7 +3,6 @@ package plugin import ( "context" "fmt" - "strings" "github.com/distribution/reference" "github.com/docker/cli/cli" @@ -80,9 +79,6 @@ func runUpgrade(ctx context.Context, dockerCLI command.Cli, opts pluginOptions) responseBody, err := dockerCLI.Client().PluginUpgrade(ctx, opts.localName, options) if err != nil { - if strings.Contains(err.Error(), "target is image") { - return errors.New(err.Error() + " - Use `docker image pull`") - } return err } defer func() { From 3bacc99580e805a248a8850622ca288aca5ca2cb Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 5 Sep 2025 13:30:47 +0200 Subject: [PATCH 147/193] cli/command/image: build: remove permissions warning on Windows This warning was added in [moby@4a8b3ca] to print a warning when building Linux images from a Windows client. Window's filesystem does not have an "executable" bit, which mean that, for example, copying a shell script to an image during build would lose the executable bit. So for Windows clients, the executable bit would be set on all files, unconditionally. Originally this was detected in the client, which had direct access to the API response headers, but when refactoring the client to use a common library in [moby@535c4c9], this was refactored into a `ImageBuildResponse` wrapper, deconstructing the API response into an `io.Reader` and a string field containing only the `OSType` header. This was the only use and only purpose of the `OSType` field, and now that BuildKit is the default builder for Linux images, this warning didn't get printed unless BuildKit was explicitly disabled. This patch removes the warning, so that we can potentially remove the field, or the `ImageBuildResponse` type altogether. [moby@4a8b3ca]: https://github.com/moby/moby/commit/4a8b3cad6096854027151dfbcfb4b2cd8841ad95 [moby@535c4c9]: https://github.com/moby/moby/commit/535c4c9a59b1e58c897677d6948a595cb3d28639 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit af65ee4584196d543a3d548de4de97b62850505e) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/cli/command/image/build.go b/cli/command/image/build.go index f7f0493d5d6f..4d54cae2f7d0 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -10,7 +10,6 @@ import ( "io" "os" "path/filepath" - "runtime" "strings" "github.com/distribution/reference" @@ -385,16 +384,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) return err } - // Windows: show error message about modified file permissions if the - // daemon isn't running Windows. - if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet { - _, _ = fmt.Fprintln(dockerCli.Out(), "SECURITY WARNING: You are building a Docker "+ - "image from Windows against a non-Windows Docker host. All files and "+ - "directories added to build context will have '-rwxr-xr-x' permissions. "+ - "It is recommended to double check and reset permissions for sensitive "+ - "files and directories.") - } - // Everything worked so if -q was provided the output from the daemon // should be just the image ID and we'll print that to stdout. if options.quiet { From 56f7bd075956af7c36a85e2abe706232a3b682aa Mon Sep 17 00:00:00 2001 From: Austin Vazquez Date: Thu, 4 Sep 2025 17:26:07 -0500 Subject: [PATCH 148/193] Set `ReservedSpace` field in preparation of `KeepStorage` deprecation This change updates the builder prune command to send the `ReservedSpace` parameter in preparation of `KeepStorage` deprecation in API v1.52. Signed-off-by: Austin Vazquez (cherry picked from commit 7d85d8fbeac5b0ab3d09e6a5317bc4c9eaff9516) Signed-off-by: Sebastiaan van Stijn --- cli/command/builder/prune.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cli/command/builder/prune.go b/cli/command/builder/prune.go index 9eef43216429..e57534cbfb19 100644 --- a/cli/command/builder/prune.go +++ b/cli/command/builder/prune.go @@ -85,9 +85,12 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) } report, err := dockerCli.Client().BuildCachePrune(ctx, build.CachePruneOptions{ - All: options.all, - KeepStorage: options.keepStorage.Value(), // FIXME(thaJeztah): rewrite to use new options; see https://github.com/moby/moby/pull/48720 - Filters: pruneFilters, + All: options.all, + // TODO(austinvazquez): remove when updated to use github.com/moby/moby/client@v0.1.0 + // See https://github.com/moby/moby/pull/50772 for more details. + KeepStorage: options.keepStorage.Value(), + ReservedSpace: options.keepStorage.Value(), + Filters: pruneFilters, }) if err != nil { return 0, "", err From 09fcf8e3dd7082cdc67c484a725cf4e7593b23e7 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 24 Sep 2025 12:10:57 +0200 Subject: [PATCH 149/193] cli/command: NewDockerCli: don't depend on DockerCli.Apply The Apply method was added when CLI options for constructing the CLI were rewritten into functional options in [cli@7f207f3]. There was no mention in the pull request of this method specifically, and this may have been related to work being done elsewhere on compose-on-kubernetes or the compose-cli plugin that may have needed options to modify the CLI config after it was already initialized. We should try to remove functions that mutate the CLI configuration after initialization if possible (and likely remove the `Apply` method); currently this function is used in docker compose, but as part of a hack that can probably be avoided. [cli@7f207f3]: https://github.com/docker/cli/commit/7f207f3f957ed3f5129aeb22bef2a429c14caf22 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 133279fb0d4adea30d27d27eb8789b79405fc82b) Signed-off-by: Sebastiaan van Stijn --- cli/command/cli.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cli/command/cli.go b/cli/command/cli.go index b657ff313d49..c62d6be3c44b 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -603,8 +603,10 @@ func NewDockerCli(ops ...CLIOption) (*DockerCli, error) { ops = append(defaultOps, ops...) cli := &DockerCli{baseCtx: context.Background()} - if err := cli.Apply(ops...); err != nil { - return nil, err + for _, op := range ops { + if err := op(cli); err != nil { + return nil, err + } } return cli, nil } From d52de77ef45c01fb001827895bc6859109089b8b Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 8 Aug 2025 22:48:10 +0200 Subject: [PATCH 150/193] vendor: github.com/docker/go-connections v0.6.0 - deprecate sockets.GetProxyEnv, sockets.DialerFromEnvironment - add support for unix sockets on Windows - remove legacy CBC cipher suites from client config - align client and server defaults to be the same. - remove support for encrypted TLS private keys. - nat: optimize ParsePortSpec full diff: https://github.com/docker/go-connections/compare/v0.5.0...v0.6.0 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 3529651fa7a4a51f8ffb01e064ff654c03dcb9c6) Signed-off-by: Sebastiaan van Stijn --- opts/swarmopts/port_test.go | 3 +- vendor.mod | 2 +- vendor.sum | 4 +- .../docker/go-connections/nat/nat.go | 149 +++++++++--------- .../docker/go-connections/nat/parse.go | 6 +- .../docker/go-connections/sockets/README.md | 0 .../docker/go-connections/sockets/proxy.go | 9 +- .../docker/go-connections/sockets/sockets.go | 31 +++- .../go-connections/sockets/sockets_unix.go | 23 +-- .../go-connections/sockets/sockets_windows.go | 7 +- .../go-connections/sockets/unix_socket.go | 44 +----- .../sockets/unix_socket_unix.go | 54 +++++++ .../sockets/unix_socket_windows.go | 7 + .../docker/go-connections/tlsconfig/config.go | 100 +++++------- .../tlsconfig/config_client_ciphers.go | 14 -- vendor/modules.txt | 2 +- 16 files changed, 224 insertions(+), 231 deletions(-) delete mode 100644 vendor/github.com/docker/go-connections/sockets/README.md create mode 100644 vendor/github.com/docker/go-connections/sockets/unix_socket_unix.go create mode 100644 vendor/github.com/docker/go-connections/sockets/unix_socket_windows.go delete mode 100644 vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go diff --git a/opts/swarmopts/port_test.go b/opts/swarmopts/port_test.go index c13c6cd4d000..a9e2f4a293a9 100644 --- a/opts/swarmopts/port_test.go +++ b/opts/swarmopts/port_test.go @@ -309,7 +309,8 @@ func TestPortOptInvalidSimpleSyntax(t *testing.T) { }, { value: "", - expectedError: "no port specified: ", + expectedError: "invalid proto: ", + // expectedError: "no port specified: ", // FIXME(thaJeztah): re-enable once https://github.com/docker/go-connections/pull/143 is in a go-connections release. }, { value: "1.1.1.1:80:80", diff --git a/vendor.mod b/vendor.mod index b9beacdf0cdc..d3ea0b714356 100644 --- a/vendor.mod +++ b/vendor.mod @@ -18,7 +18,7 @@ require ( github.com/docker/distribution v2.8.3+incompatible github.com/docker/docker v28.4.0+incompatible github.com/docker/docker-credential-helpers v0.9.3 - github.com/docker/go-connections v0.5.0 + github.com/docker/go-connections v0.6.0 github.com/docker/go-units v0.5.0 github.com/fvbommel/sortorder v1.1.0 github.com/go-jose/go-jose/v4 v4.0.5 diff --git a/vendor.sum b/vendor.sum index 23f314205ca4..c5303fbbfc68 100644 --- a/vendor.sum +++ b/vendor.sum @@ -64,8 +64,8 @@ github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 h1:EHZfspsnLAz8Hzccd67D5abwLiqoqym2jz/jOS39mCk= github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= diff --git a/vendor/github.com/docker/go-connections/nat/nat.go b/vendor/github.com/docker/go-connections/nat/nat.go index 4049d780c54a..1ffe0355dc15 100644 --- a/vendor/github.com/docker/go-connections/nat/nat.go +++ b/vendor/github.com/docker/go-connections/nat/nat.go @@ -2,6 +2,7 @@ package nat import ( + "errors" "fmt" "net" "strconv" @@ -43,19 +44,19 @@ func NewPort(proto, port string) (Port, error) { // ParsePort parses the port number string and returns an int func ParsePort(rawPort string) (int, error) { - if len(rawPort) == 0 { + if rawPort == "" { return 0, nil } port, err := strconv.ParseUint(rawPort, 10, 16) if err != nil { - return 0, err + return 0, fmt.Errorf("invalid port '%s': %w", rawPort, errors.Unwrap(err)) } return int(port), nil } // ParsePortRangeToInt parses the port range string and returns start/end ints func ParsePortRangeToInt(rawPort string) (int, int, error) { - if len(rawPort) == 0 { + if rawPort == "" { return 0, 0, nil } start, end, err := ParsePortRange(rawPort) @@ -91,29 +92,31 @@ func (p Port) Range() (int, int, error) { return ParsePortRangeToInt(p.Port()) } -// SplitProtoPort splits a port in the format of proto/port -func SplitProtoPort(rawPort string) (string, string) { - parts := strings.Split(rawPort, "/") - l := len(parts) - if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 { +// SplitProtoPort splits a port(range) and protocol, formatted as "/[]" +// "/[]". It returns an empty string for both if +// no port(range) is provided. If a port(range) is provided, but no protocol, +// the default ("tcp") protocol is returned. +// +// SplitProtoPort does not validate or normalize the returned values. +func SplitProtoPort(rawPort string) (proto string, port string) { + port, proto, _ = strings.Cut(rawPort, "/") + if port == "" { return "", "" } - if l == 1 { - return "tcp", rawPort + if proto == "" { + proto = "tcp" } - if len(parts[1]) == 0 { - return "tcp", parts[0] - } - return parts[1], parts[0] + return proto, port } -func validateProto(proto string) bool { - for _, availableProto := range []string{"tcp", "udp", "sctp"} { - if availableProto == proto { - return true - } +func validateProto(proto string) error { + switch proto { + case "tcp", "udp", "sctp": + // All good + return nil + default: + return errors.New("invalid proto: " + proto) } - return false } // ParsePortSpecs receives port specs in the format of ip:public:private/proto and parses @@ -123,22 +126,18 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, exposedPorts = make(map[Port]struct{}, len(ports)) bindings = make(map[Port][]PortBinding) ) - for _, rawPort := range ports { - portMappings, err := ParsePortSpec(rawPort) + for _, p := range ports { + portMappings, err := ParsePortSpec(p) if err != nil { return nil, nil, err } - for _, portMapping := range portMappings { - port := portMapping.Port - if _, exists := exposedPorts[port]; !exists { + for _, pm := range portMappings { + port := pm.Port + if _, ok := exposedPorts[port]; !ok { exposedPorts[port] = struct{}{} } - bslice, exists := bindings[port] - if !exists { - bslice = []PortBinding{} - } - bindings[port] = append(bslice, portMapping.Binding) + bindings[port] = append(bindings[port], pm.Binding) } } return exposedPorts, bindings, nil @@ -150,28 +149,34 @@ type PortMapping struct { Binding PortBinding } -func splitParts(rawport string) (string, string, string) { +func (p *PortMapping) String() string { + return net.JoinHostPort(p.Binding.HostIP, p.Binding.HostPort+":"+string(p.Port)) +} + +func splitParts(rawport string) (hostIP, hostPort, containerPort string) { parts := strings.Split(rawport, ":") - n := len(parts) - containerPort := parts[n-1] - switch n { + switch len(parts) { case 1: - return "", "", containerPort + return "", "", parts[0] case 2: - return "", parts[0], containerPort + return "", parts[0], parts[1] case 3: - return parts[0], parts[1], containerPort + return parts[0], parts[1], parts[2] default: - return strings.Join(parts[:n-2], ":"), parts[n-2], containerPort + n := len(parts) + return strings.Join(parts[:n-2], ":"), parts[n-2], parts[n-1] } } // ParsePortSpec parses a port specification string into a slice of PortMappings func ParsePortSpec(rawPort string) ([]PortMapping, error) { - var proto string ip, hostPort, containerPort := splitParts(rawPort) - proto, containerPort = SplitProtoPort(containerPort) + proto, containerPort := SplitProtoPort(containerPort) + proto = strings.ToLower(proto) + if err := validateProto(proto); err != nil { + return nil, err + } if ip != "" && ip[0] == '[' { // Strip [] from IPV6 addresses @@ -182,7 +187,7 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) { ip = rawIP } if ip != "" && net.ParseIP(ip) == nil { - return nil, fmt.Errorf("invalid IP address: %s", ip) + return nil, errors.New("invalid IP address: " + ip) } if containerPort == "" { return nil, fmt.Errorf("no port specified: %s", rawPort) @@ -190,51 +195,43 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) { startPort, endPort, err := ParsePortRange(containerPort) if err != nil { - return nil, fmt.Errorf("invalid containerPort: %s", containerPort) + return nil, errors.New("invalid containerPort: " + containerPort) } - var startHostPort, endHostPort uint64 = 0, 0 - if len(hostPort) > 0 { + var startHostPort, endHostPort uint64 + if hostPort != "" { startHostPort, endHostPort, err = ParsePortRange(hostPort) if err != nil { - return nil, fmt.Errorf("invalid hostPort: %s", hostPort) + return nil, errors.New("invalid hostPort: " + hostPort) } - } - - if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) { - // Allow host port range iff containerPort is not a range. - // In this case, use the host port range as the dynamic - // host port range to allocate into. - if endPort != startPort { - return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) + if (endPort - startPort) != (endHostPort - startHostPort) { + // Allow host port range iff containerPort is not a range. + // In this case, use the host port range as the dynamic + // host port range to allocate into. + if endPort != startPort { + return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) + } } } - if !validateProto(strings.ToLower(proto)) { - return nil, fmt.Errorf("invalid proto: %s", proto) - } - - ports := []PortMapping{} - for i := uint64(0); i <= (endPort - startPort); i++ { - containerPort = strconv.FormatUint(startPort+i, 10) - if len(hostPort) > 0 { - hostPort = strconv.FormatUint(startHostPort+i, 10) - } - // Set hostPort to a range only if there is a single container port - // and a dynamic host port. - if startPort == endPort && startHostPort != endHostPort { - hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10)) - } - port, err := NewPort(strings.ToLower(proto), containerPort) - if err != nil { - return nil, err - } + count := endPort - startPort + 1 + ports := make([]PortMapping, 0, count) - binding := PortBinding{ - HostIP: ip, - HostPort: hostPort, + for i := uint64(0); i < count; i++ { + cPort := Port(strconv.FormatUint(startPort+i, 10) + "/" + proto) + hPort := "" + if hostPort != "" { + hPort = strconv.FormatUint(startHostPort+i, 10) + // Set hostPort to a range only if there is a single container port + // and a dynamic host port. + if count == 1 && startHostPort != endHostPort { + hPort += "-" + strconv.FormatUint(endHostPort, 10) + } } - ports = append(ports, PortMapping{Port: port, Binding: binding}) + ports = append(ports, PortMapping{ + Port: cPort, + Binding: PortBinding{HostIP: ip, HostPort: hPort}, + }) } return ports, nil } diff --git a/vendor/github.com/docker/go-connections/nat/parse.go b/vendor/github.com/docker/go-connections/nat/parse.go index e4b53e8a3242..64affa2a904c 100644 --- a/vendor/github.com/docker/go-connections/nat/parse.go +++ b/vendor/github.com/docker/go-connections/nat/parse.go @@ -1,7 +1,7 @@ package nat import ( - "fmt" + "errors" "strconv" "strings" ) @@ -9,7 +9,7 @@ import ( // ParsePortRange parses and validates the specified string as a port-range (8000-9000) func ParsePortRange(ports string) (uint64, uint64, error) { if ports == "" { - return 0, 0, fmt.Errorf("empty string specified for ports") + return 0, 0, errors.New("empty string specified for ports") } if !strings.Contains(ports, "-") { start, err := strconv.ParseUint(ports, 10, 16) @@ -27,7 +27,7 @@ func ParsePortRange(ports string) (uint64, uint64, error) { return 0, 0, err } if end < start { - return 0, 0, fmt.Errorf("invalid range specified for port: %s", ports) + return 0, 0, errors.New("invalid range specified for port: " + ports) } return start, end, nil } diff --git a/vendor/github.com/docker/go-connections/sockets/README.md b/vendor/github.com/docker/go-connections/sockets/README.md deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/vendor/github.com/docker/go-connections/sockets/proxy.go b/vendor/github.com/docker/go-connections/sockets/proxy.go index c897cb02adea..f04980e40a5a 100644 --- a/vendor/github.com/docker/go-connections/sockets/proxy.go +++ b/vendor/github.com/docker/go-connections/sockets/proxy.go @@ -9,6 +9,8 @@ import ( // GetProxyEnv allows access to the uppercase and the lowercase forms of // proxy-related variables. See the Go specification for details on these // variables. https://golang.org/pkg/net/http/ +// +// Deprecated: this function was used as helper for [DialerFromEnvironment] and is no longer used. It will be removed in the next release. func GetProxyEnv(key string) string { proxyValue := os.Getenv(strings.ToUpper(key)) if proxyValue == "" { @@ -19,10 +21,11 @@ func GetProxyEnv(key string) string { // DialerFromEnvironment was previously used to configure a net.Dialer to route // connections through a SOCKS proxy. -// DEPRECATED: SOCKS proxies are now supported by configuring only +// +// Deprecated: SOCKS proxies are now supported by configuring only // http.Transport.Proxy, and no longer require changing http.Transport.Dial. -// Therefore, only sockets.ConfigureTransport() needs to be called, and any -// sockets.DialerFromEnvironment() calls can be dropped. +// Therefore, only [sockets.ConfigureTransport] needs to be called, and any +// [sockets.DialerFromEnvironment] calls can be dropped. func DialerFromEnvironment(direct *net.Dialer) (*net.Dialer, error) { return direct, nil } diff --git a/vendor/github.com/docker/go-connections/sockets/sockets.go b/vendor/github.com/docker/go-connections/sockets/sockets.go index b0eae239d2c5..6117297860db 100644 --- a/vendor/github.com/docker/go-connections/sockets/sockets.go +++ b/vendor/github.com/docker/go-connections/sockets/sockets.go @@ -2,13 +2,19 @@ package sockets import ( + "context" "errors" + "fmt" "net" "net/http" + "syscall" "time" ) -const defaultTimeout = 10 * time.Second +const ( + defaultTimeout = 10 * time.Second + maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path) +) // ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system. var ErrProtocolNotAvailable = errors.New("protocol not available") @@ -35,3 +41,26 @@ func ConfigureTransport(tr *http.Transport, proto, addr string) error { } return nil } + +// DialPipe connects to a Windows named pipe. It is not supported on +// non-Windows platforms. +// +// Deprecated: use [github.com/Microsoft/go-winio.DialPipe] or [github.com/Microsoft/go-winio.DialPipeContext]. +func DialPipe(addr string, timeout time.Duration) (net.Conn, error) { + return dialPipe(addr, timeout) +} + +func configureUnixTransport(tr *http.Transport, proto, addr string) error { + if len(addr) > maxUnixSocketPathSize { + return fmt.Errorf("unix socket path %q is too long", addr) + } + // No need for compression in local communications. + tr.DisableCompression = true + dialer := &net.Dialer{ + Timeout: defaultTimeout, + } + tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) { + return dialer.DialContext(ctx, proto, addr) + } + return nil +} diff --git a/vendor/github.com/docker/go-connections/sockets/sockets_unix.go b/vendor/github.com/docker/go-connections/sockets/sockets_unix.go index 78a34a980d28..913d2f00dd2f 100644 --- a/vendor/github.com/docker/go-connections/sockets/sockets_unix.go +++ b/vendor/github.com/docker/go-connections/sockets/sockets_unix.go @@ -3,37 +3,16 @@ package sockets import ( - "context" - "fmt" "net" "net/http" "syscall" "time" ) -const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path) - -func configureUnixTransport(tr *http.Transport, proto, addr string) error { - if len(addr) > maxUnixSocketPathSize { - return fmt.Errorf("unix socket path %q is too long", addr) - } - // No need for compression in local communications. - tr.DisableCompression = true - dialer := &net.Dialer{ - Timeout: defaultTimeout, - } - tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) { - return dialer.DialContext(ctx, proto, addr) - } - return nil -} - func configureNpipeTransport(tr *http.Transport, proto, addr string) error { return ErrProtocolNotAvailable } -// DialPipe connects to a Windows named pipe. -// This is not supported on other OSes. -func DialPipe(_ string, _ time.Duration) (net.Conn, error) { +func dialPipe(_ string, _ time.Duration) (net.Conn, error) { return nil, syscall.EAFNOSUPPORT } diff --git a/vendor/github.com/docker/go-connections/sockets/sockets_windows.go b/vendor/github.com/docker/go-connections/sockets/sockets_windows.go index 7acafc5a2ad8..6d6beb3855c0 100644 --- a/vendor/github.com/docker/go-connections/sockets/sockets_windows.go +++ b/vendor/github.com/docker/go-connections/sockets/sockets_windows.go @@ -9,10 +9,6 @@ import ( "github.com/Microsoft/go-winio" ) -func configureUnixTransport(tr *http.Transport, proto, addr string) error { - return ErrProtocolNotAvailable -} - func configureNpipeTransport(tr *http.Transport, proto, addr string) error { // No need for compression in local communications. tr.DisableCompression = true @@ -22,7 +18,6 @@ func configureNpipeTransport(tr *http.Transport, proto, addr string) error { return nil } -// DialPipe connects to a Windows named pipe. -func DialPipe(addr string, timeout time.Duration) (net.Conn, error) { +func dialPipe(addr string, timeout time.Duration) (net.Conn, error) { return winio.DialPipe(addr, &timeout) } diff --git a/vendor/github.com/docker/go-connections/sockets/unix_socket.go b/vendor/github.com/docker/go-connections/sockets/unix_socket.go index b9233521e49a..e736f71d38b1 100644 --- a/vendor/github.com/docker/go-connections/sockets/unix_socket.go +++ b/vendor/github.com/docker/go-connections/sockets/unix_socket.go @@ -1,5 +1,3 @@ -//go:build !windows - /* Package sockets is a simple unix domain socket wrapper. @@ -57,26 +55,6 @@ import ( // SockOption sets up socket file's creating option type SockOption func(string) error -// WithChown modifies the socket file's uid and gid -func WithChown(uid, gid int) SockOption { - return func(path string) error { - if err := os.Chown(path, uid, gid); err != nil { - return err - } - return nil - } -} - -// WithChmod modifies socket file's access mode. -func WithChmod(mask os.FileMode) SockOption { - return func(path string) error { - if err := os.Chmod(path, mask); err != nil { - return err - } - return nil - } -} - // NewUnixSocketWithOpts creates a unix socket with the specified options. // By default, socket permissions are 0000 (i.e.: no access for anyone); pass // WithChmod() and WithChown() to set the desired ownership and permissions. @@ -90,22 +68,7 @@ func NewUnixSocketWithOpts(path string, opts ...SockOption) (net.Listener, error return nil, err } - // net.Listen does not allow for permissions to be set. As a result, when - // specifying custom permissions ("WithChmod()"), there is a short time - // between creating the socket and applying the permissions, during which - // the socket permissions are Less restrictive than desired. - // - // To work around this limitation of net.Listen(), we temporarily set the - // umask to 0777, which forces the socket to be created with 000 permissions - // (i.e.: no access for anyone). After that, WithChmod() must be used to set - // the desired permissions. - // - // We don't use "defer" here, to reset the umask to its original value as soon - // as possible. Ideally we'd be able to detect if WithChmod() was passed as - // an option, and skip changing umask if default permissions are used. - origUmask := syscall.Umask(0o777) - l, err := net.Listen("unix", path) - syscall.Umask(origUmask) + l, err := listenUnix(path) if err != nil { return nil, err } @@ -119,8 +82,3 @@ func NewUnixSocketWithOpts(path string, opts ...SockOption) (net.Listener, error return l, nil } - -// NewUnixSocket creates a unix socket with the specified path and group. -func NewUnixSocket(path string, gid int) (net.Listener, error) { - return NewUnixSocketWithOpts(path, WithChown(0, gid), WithChmod(0o660)) -} diff --git a/vendor/github.com/docker/go-connections/sockets/unix_socket_unix.go b/vendor/github.com/docker/go-connections/sockets/unix_socket_unix.go new file mode 100644 index 000000000000..a41a71654742 --- /dev/null +++ b/vendor/github.com/docker/go-connections/sockets/unix_socket_unix.go @@ -0,0 +1,54 @@ +//go:build !windows + +package sockets + +import ( + "net" + "os" + "syscall" +) + +// WithChown modifies the socket file's uid and gid +func WithChown(uid, gid int) SockOption { + return func(path string) error { + if err := os.Chown(path, uid, gid); err != nil { + return err + } + return nil + } +} + +// WithChmod modifies socket file's access mode. +func WithChmod(mask os.FileMode) SockOption { + return func(path string) error { + if err := os.Chmod(path, mask); err != nil { + return err + } + return nil + } +} + +// NewUnixSocket creates a unix socket with the specified path and group. +func NewUnixSocket(path string, gid int) (net.Listener, error) { + return NewUnixSocketWithOpts(path, WithChown(0, gid), WithChmod(0o660)) +} + +func listenUnix(path string) (net.Listener, error) { + // net.Listen does not allow for permissions to be set. As a result, when + // specifying custom permissions ("WithChmod()"), there is a short time + // between creating the socket and applying the permissions, during which + // the socket permissions are Less restrictive than desired. + // + // To work around this limitation of net.Listen(), we temporarily set the + // umask to 0777, which forces the socket to be created with 000 permissions + // (i.e.: no access for anyone). After that, WithChmod() must be used to set + // the desired permissions. + // + // We don't use "defer" here, to reset the umask to its original value as soon + // as possible. Ideally we'd be able to detect if WithChmod() was passed as + // an option, and skip changing umask if default permissions are used. + origUmask := syscall.Umask(0o777) + l, err := net.Listen("unix", path) + syscall.Umask(origUmask) + return l, err +} diff --git a/vendor/github.com/docker/go-connections/sockets/unix_socket_windows.go b/vendor/github.com/docker/go-connections/sockets/unix_socket_windows.go new file mode 100644 index 000000000000..5ec29e059e78 --- /dev/null +++ b/vendor/github.com/docker/go-connections/sockets/unix_socket_windows.go @@ -0,0 +1,7 @@ +package sockets + +import "net" + +func listenUnix(path string) (net.Listener, error) { + return net.Listen("unix", path) +} diff --git a/vendor/github.com/docker/go-connections/tlsconfig/config.go b/vendor/github.com/docker/go-connections/tlsconfig/config.go index 606c98a38b51..8b0264f68b75 100644 --- a/vendor/github.com/docker/go-connections/tlsconfig/config.go +++ b/vendor/github.com/docker/go-connections/tlsconfig/config.go @@ -34,51 +34,37 @@ type Options struct { // the system pool will be used. ExclusiveRootPools bool MinVersion uint16 - // If Passphrase is set, it will be used to decrypt a TLS private key - // if the key is encrypted. - // - // Deprecated: Use of encrypted TLS private keys has been deprecated, and - // will be removed in a future release. Golang has deprecated support for - // legacy PEM encryption (as specified in RFC 1423), as it is insecure by - // design (see https://go-review.googlesource.com/c/go/+/264159). - Passphrase string -} - -// Extra (server-side) accepted CBC cipher suites - will phase out in the future -var acceptedCBCCiphers = []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, } // DefaultServerAcceptedCiphers should be uses by code which already has a crypto/tls // options struct but wants to use a commonly accepted set of TLS cipher suites, with // known weak algorithms removed. -var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...) +var DefaultServerAcceptedCiphers = defaultCipherSuites + +// defaultCipherSuites is shared by both client and server as the default set. +var defaultCipherSuites = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, +} // ServerDefault returns a secure-enough TLS configuration for the server TLS configuration. func ServerDefault(ops ...func(*tls.Config)) *tls.Config { - tlsConfig := &tls.Config{ - // Avoid fallback by default to SSL protocols < TLS1.2 - MinVersion: tls.VersionTLS12, - PreferServerCipherSuites: true, - CipherSuites: DefaultServerAcceptedCiphers, - } - - for _, op := range ops { - op(tlsConfig) - } - - return tlsConfig + return defaultConfig(ops...) } // ClientDefault returns a secure-enough TLS configuration for the client TLS configuration. func ClientDefault(ops ...func(*tls.Config)) *tls.Config { + return defaultConfig(ops...) +} + +// defaultConfig is the default config used by both client and server TLS configuration. +func defaultConfig(ops ...func(*tls.Config)) *tls.Config { tlsConfig := &tls.Config{ - // Prefer TLS1.2 as the client minimum + // Avoid fallback by default to SSL protocols < TLS1.2 MinVersion: tls.VersionTLS12, - CipherSuites: clientCipherSuites, + CipherSuites: defaultCipherSuites, } for _, op := range ops { @@ -92,13 +78,13 @@ func ClientDefault(ops ...func(*tls.Config)) *tls.Config { func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) { // If we should verify the server, we need to load a trusted ca var ( - certPool *x509.CertPool - err error + pool *x509.CertPool + err error ) if exclusivePool { - certPool = x509.NewCertPool() + pool = x509.NewCertPool() } else { - certPool, err = SystemCertPool() + pool, err = SystemCertPool() if err != nil { return nil, fmt.Errorf("failed to read system certificates: %v", err) } @@ -107,10 +93,10 @@ func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) { if err != nil { return nil, fmt.Errorf("could not read CA certificate %q: %v", caFile, err) } - if !certPool.AppendCertsFromPEM(pemData) { + if !pool.AppendCertsFromPEM(pemData) { return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile) } - return certPool, nil + return pool, nil } // allTLSVersions lists all the TLS versions and is used by the code that validates @@ -144,34 +130,32 @@ func adjustMinVersion(options Options, config *tls.Config) error { return nil } -// IsErrEncryptedKey returns true if the 'err' is an error of incorrect -// password when trying to decrypt a TLS private key. +// errEncryptedKeyDeprecated is produced when we encounter an encrypted +// (password-protected) key. From https://go-review.googlesource.com/c/go/+/264159; // -// Deprecated: Use of encrypted TLS private keys has been deprecated, and -// will be removed in a future release. Golang has deprecated support for -// legacy PEM encryption (as specified in RFC 1423), as it is insecure by -// design (see https://go-review.googlesource.com/c/go/+/264159). -func IsErrEncryptedKey(err error) bool { - return errors.Is(err, x509.IncorrectPasswordError) -} +// > Legacy PEM encryption as specified in RFC 1423 is insecure by design. Since +// > it does not authenticate the ciphertext, it is vulnerable to padding oracle +// > attacks that can let an attacker recover the plaintext +// > +// > It's unfortunate that we don't implement PKCS#8 encryption so we can't +// > recommend an alternative but PEM encryption is so broken that it's worth +// > deprecating outright. +// +// Also see https://docs.docker.com/go/deprecated/ +var errEncryptedKeyDeprecated = errors.New("private key is encrypted; encrypted private keys are obsolete, and not supported") // getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format. -// If the private key is encrypted, 'passphrase' is used to decrypted the -// private key. -func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) { +// It returns an error if the file could not be decoded or was protected by +// a passphrase. +func getPrivateKey(keyBytes []byte) ([]byte, error) { // this section makes some small changes to code from notary/tuf/utils/x509.go pemBlock, _ := pem.Decode(keyBytes) if pemBlock == nil { return nil, fmt.Errorf("no valid private key found") } - var err error if x509.IsEncryptedPEMBlock(pemBlock) { //nolint:staticcheck // Ignore SA1019 (IsEncryptedPEMBlock is deprecated) - keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(passphrase)) //nolint:staticcheck // Ignore SA1019 (DecryptPEMBlock is deprecated) - if err != nil { - return nil, fmt.Errorf("private key is encrypted, but could not decrypt it: %w", err) - } - keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes}) + return nil, errEncryptedKeyDeprecated } return keyBytes, nil @@ -195,7 +179,7 @@ func getCert(options Options) ([]tls.Certificate, error) { return nil, err } - prKeyBytes, err = getPrivateKey(prKeyBytes, options.Passphrase) + prKeyBytes, err = getPrivateKey(prKeyBytes) if err != nil { return nil, err } @@ -210,7 +194,7 @@ func getCert(options Options) ([]tls.Certificate, error) { // Client returns a TLS configuration meant to be used by a client. func Client(options Options) (*tls.Config, error) { - tlsConfig := ClientDefault() + tlsConfig := defaultConfig() tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify if !options.InsecureSkipVerify && options.CAFile != "" { CAs, err := certPool(options.CAFile, options.ExclusiveRootPools) @@ -235,7 +219,7 @@ func Client(options Options) (*tls.Config, error) { // Server returns a TLS configuration meant to be used by a server. func Server(options Options) (*tls.Config, error) { - tlsConfig := ServerDefault() + tlsConfig := defaultConfig() tlsConfig.ClientAuth = options.ClientAuth tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) if err != nil { diff --git a/vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go b/vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go deleted file mode 100644 index a82f9fa52e2e..000000000000 --- a/vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go +++ /dev/null @@ -1,14 +0,0 @@ -// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers. -package tlsconfig - -import ( - "crypto/tls" -) - -// Client TLS cipher suites (dropping CBC ciphers for client preferred suite set) -var clientCipherSuites = []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 77e1dfc939a4..712d74a15b2d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -101,7 +101,7 @@ github.com/docker/docker-credential-helpers/credentials # github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c ## explicit github.com/docker/go/canonical/json -# github.com/docker/go-connections v0.5.0 +# github.com/docker/go-connections v0.6.0 ## explicit; go 1.18 github.com/docker/go-connections/nat github.com/docker/go-connections/sockets From d1122a229367ae10bfa8aa6f4973f792d552590b Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 11 Mar 2025 11:27:20 +0100 Subject: [PATCH 151/193] cli-plugins/manager: ignore broken symlinks Before this patch, a broken symlink would print a warning; docker info > /dev/null WARNING: Plugin "/Users/thajeztah/.docker/cli-plugins/docker-feedback" is not valid: failed to fetch metadata: fork/exec /Users/thajeztah/.docker/cli-plugins/docker-feedback: no such file or directory After this patch, such symlinks are ignored: docker info > /dev/null With debug enabled, we don't ignore the faulty plugin, which will make the warning shown on docker info; mkdir -p ~/.docker/cli-plugins ln -s nosuchplugin ~/.docker/cli-plugins/docker-brokenplugin docker --debug info Client: Version: 29.0.0-dev Context: default Debug Mode: true Plugins: buildx: Docker Buildx (Docker Inc.) Version: v0.25.0 Path: /usr/libexec/docker/cli-plugins/docker-buildx WARNING: Plugin "/Users/thajeztah/.docker/cli-plugins/docker-brokenplugin" is not valid: failed to fetch metadata: fork/exec /Users/thajeztah/.docker/cli-plugins/docker-brokenplugin: no such file or directory # ... We should als consider passing a "seen" map to de-duplicate entries. Entries can be either a direct symlink or in a symlinked path (for which we can filepath.EvalSymlinks). We need to benchmark the overhead of resolving the symlink vs possibly calling the plugin (to get their metadata) further down the line. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 9b2f8314523059e3db1b5909ced528de0d6af34b) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/manager.go | 16 +++++++++++++--- cli-plugins/manager/manager_test.go | 5 +---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/cli-plugins/manager/manager.go b/cli-plugins/manager/manager.go index bab031811761..c8ea15de395b 100644 --- a/cli-plugins/manager/manager.go +++ b/cli-plugins/manager/manager.go @@ -2,6 +2,7 @@ package manager import ( "context" + "errors" "os" "os/exec" "path/filepath" @@ -13,6 +14,7 @@ import ( "github.com/docker/cli/cli-plugins/metadata" "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/debug" "github.com/fvbommel/sortorder" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" @@ -74,9 +76,17 @@ func addPluginCandidatesFromDir(res map[string][]string, d string) { return } for _, dentry := range dentries { - switch dentry.Type() & os.ModeType { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list - case 0, os.ModeSymlink: - // Regular file or symlink, keep going + switch mode := dentry.Type() & os.ModeType; mode { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list + case os.ModeSymlink: + if !debug.IsEnabled() { + // Skip broken symlinks unless debug is enabled. With debug + // enabled, this will print a warning in "docker info". + if _, err := os.Stat(filepath.Join(d, dentry.Name())); errors.Is(err, os.ErrNotExist) { + continue + } + } + case 0: + // Regular file, keep going default: // Something else, ignore. continue diff --git a/cli-plugins/manager/manager_test.go b/cli-plugins/manager/manager_test.go index d1223ed4ec86..16c43b29d338 100644 --- a/cli-plugins/manager/manager_test.go +++ b/cli-plugins/manager/manager_test.go @@ -38,7 +38,7 @@ func TestListPluginCandidates(t *testing.T) { "plugins3-target", // Will be referenced as a symlink from below fs.WithFile("docker-plugin1", ""), fs.WithDir("ignored3"), - fs.WithSymlink("docker-brokensymlink", "broken"), // A broken symlink is still a candidate (but would fail tests later) + fs.WithSymlink("docker-brokensymlink", "broken"), // A broken symlink is ignored fs.WithFile("non-plugin-symlinked", ""), // This shouldn't appear, but ... fs.WithSymlink("docker-symlinked", "non-plugin-symlinked"), // ... this link to it should. ), @@ -72,9 +72,6 @@ func TestListPluginCandidates(t *testing.T) { "hardlink2": { dir.Join("plugins2", "docker-hardlink2"), }, - "brokensymlink": { - dir.Join("plugins3", "docker-brokensymlink"), - }, "symlinked": { dir.Join("plugins3", "docker-symlinked"), }, From 5d201ca4368385b385540f7abe0ab232419c0cde Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 24 Sep 2025 12:57:42 +0200 Subject: [PATCH 152/193] cli-plugins/plugin: Run: touch-up godoc and minor cleanups Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 635a7182099445e9136f2ab10bb441cc81cb205c) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/plugin/plugin.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/cli-plugins/plugin/plugin.go b/cli-plugins/plugin/plugin.go index f4c80b3a48f3..9f307d1d2ed1 100644 --- a/cli-plugins/plugin/plugin.go +++ b/cli-plugins/plugin/plugin.go @@ -80,19 +80,22 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadat return cmd.Execute() } -// Run is the top-level entry point to the CLI plugin framework. It should be called from your plugin's `main()` function. +// Run is the top-level entry point to the CLI plugin framework. It should +// be called from the plugin's "main()" function. It initializes a new +// [command.DockerCli] instance before calling makeCmd to construct the +// plugin command, then invokes the plugin command using [RunPlugin]. func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) { otel.SetErrorHandler(debug.OTELErrorHandler) - dockerCli, err := command.NewDockerCli() + dockerCLI, err := command.NewDockerCli() if err != nil { - fmt.Fprintln(os.Stderr, err) + _, _ = fmt.Fprintln(os.Stderr, err) os.Exit(1) } - plugin := makeCmd(dockerCli) + plugin := makeCmd(dockerCLI) - if err := RunPlugin(dockerCli, plugin, meta); err != nil { + if err := RunPlugin(dockerCLI, plugin, meta); err != nil { var stErr cli.StatusError if errors.As(err, &stErr) { // StatusError should only be used for errors, and all errors should @@ -100,10 +103,10 @@ func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) { if stErr.StatusCode == 0 { // FIXME(thaJeztah): this should never be used with a zero status-code. Check if we do this anywhere. stErr.StatusCode = 1 } - _, _ = fmt.Fprintln(dockerCli.Err(), stErr) + _, _ = fmt.Fprintln(dockerCLI.Err(), stErr) os.Exit(stErr.StatusCode) } - _, _ = fmt.Fprintln(dockerCli.Err(), err) + _, _ = fmt.Fprintln(dockerCLI.Err(), err) os.Exit(1) } } From 4bac500fb23e193b3d5aa547b60e615fd1a7111d Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 3 Aug 2025 17:50:16 +0200 Subject: [PATCH 153/193] remove some remnants from CLI "experimental" config option Experimental is always enabled (977d3ae046ec6c64be8788a8712251ed547a2bdb), and the `Experimental` field in plugin metadata was deprecated in 977d3ae046ec6c64be8788a8712251ed547a2bdb and removed in commit 6a50c4f70054cf6e60124d911e4ca8754617e21d. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit dfbac70efab64cdff81a98719e2a99353c275782) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/candidate_test.go | 10 +++++----- e2e/internal/fixtures/fixtures.go | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cli-plugins/manager/candidate_test.go b/cli-plugins/manager/candidate_test.go index e8df42909949..b54f6a967a31 100644 --- a/cli-plugins/manager/candidate_test.go +++ b/cli-plugins/manager/candidate_test.go @@ -36,10 +36,9 @@ func TestValidateCandidate(t *testing.T) { builtinName = metadata.NamePrefix + "builtin" builtinAlias = metadata.NamePrefix + "alias" - badPrefixPath = "/usr/local/libexec/cli-plugins/wobble" - badNamePath = "/usr/local/libexec/cli-plugins/docker-123456" - goodPluginPath = "/usr/local/libexec/cli-plugins/" + goodPluginName - metaExperimental = `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}` + badPrefixPath = "/usr/local/libexec/cli-plugins/wobble" + badNamePath = "/usr/local/libexec/cli-plugins/docker-123456" + goodPluginPath = "/usr/local/libexec/cli-plugins/" + goodPluginName ) fakeroot := &cobra.Command{Use: "docker"} @@ -72,7 +71,8 @@ func TestValidateCandidate(t *testing.T) { {name: "empty vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`}, invalid: "plugin metadata does not define a vendor"}, // This one should work {name: "valid", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`}}, - {name: "experimental + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental}}, + // Including the deprecated "experimental" field should not break processing. + {name: "with legacy experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`}}, } { t.Run(tc.name, func(t *testing.T) { p, err := newPlugin(tc.c, fakeroot.Commands()) diff --git a/e2e/internal/fixtures/fixtures.go b/e2e/internal/fixtures/fixtures.go index f6c49f50c794..3c47f8d7ff04 100644 --- a/e2e/internal/fixtures/fixtures.go +++ b/e2e/internal/fixtures/fixtures.go @@ -44,8 +44,7 @@ func SetupConfigWithNotaryURL(t *testing.T, path, notaryURL string) fs.Dir { "%s": { "auth": "ZWlhaXM6cGFzc3dvcmQK" } - }, - "experimental": "enabled" + } } `, notaryURL)), fs.WithDir("trust", fs.WithDir("private"))) return *dir From 2ed42a8ade44cfa96e428e250731f3ff37e637e9 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 4 Aug 2025 08:50:07 +0200 Subject: [PATCH 154/193] cli-plugins/manager: reformat TestValidateCandidate table Slightly more verbose, but makes it easier to see properties of each test. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 057f3128b6ebd97c2b51b83cafd1ffec565c311d) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/candidate_test.go | 94 ++++++++++++++++++++------- 1 file changed, 72 insertions(+), 22 deletions(-) diff --git a/cli-plugins/manager/candidate_test.go b/cli-plugins/manager/candidate_test.go index b54f6a967a31..ee7ff788b554 100644 --- a/cli-plugins/manager/candidate_test.go +++ b/cli-plugins/manager/candidate_test.go @@ -32,9 +32,8 @@ func (c *fakeCandidate) Metadata() ([]byte, error) { func TestValidateCandidate(t *testing.T) { const ( goodPluginName = metadata.NamePrefix + "goodplugin" - - builtinName = metadata.NamePrefix + "builtin" - builtinAlias = metadata.NamePrefix + "alias" + builtinName = metadata.NamePrefix + "builtin" + builtinAlias = metadata.NamePrefix + "alias" badPrefixPath = "/usr/local/libexec/cli-plugins/wobble" badNamePath = "/usr/local/libexec/cli-plugins/docker-123456" @@ -50,32 +49,83 @@ func TestValidateCandidate(t *testing.T) { }) for _, tc := range []struct { - name string - c *fakeCandidate + name string + plugin *fakeCandidate // Either err or invalid may be non-empty, but not both (both can be empty for a good plugin). err string invalid string }{ - /* Each failing one of the tests */ - {name: "empty path", c: &fakeCandidate{path: ""}, err: "plugin candidate path cannot be empty"}, - {name: "bad prefix", c: &fakeCandidate{path: badPrefixPath}, err: fmt.Sprintf("does not have %q prefix", metadata.NamePrefix)}, - {name: "bad path", c: &fakeCandidate{path: badNamePath}, invalid: "did not match"}, - {name: "builtin command", c: &fakeCandidate{path: builtinName}, invalid: `plugin "builtin" duplicates builtin command`}, - {name: "builtin alias", c: &fakeCandidate{path: builtinAlias}, invalid: `plugin "alias" duplicates an alias of builtin command "builtin"`}, - {name: "fetch failure", c: &fakeCandidate{path: goodPluginPath, exec: false}, invalid: fmt.Sprintf("failed to fetch metadata: faked a failure to exec %q", goodPluginPath)}, - {name: "metadata not json", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `xyzzy`}, invalid: "invalid character"}, - {name: "empty schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`}, invalid: `plugin SchemaVersion "" is not valid`}, - {name: "invalid schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`}, invalid: `plugin SchemaVersion "xyzzy" is not valid`}, - {name: "no vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`}, invalid: "plugin metadata does not define a vendor"}, - {name: "empty vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`}, invalid: "plugin metadata does not define a vendor"}, - // This one should work - {name: "valid", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`}}, - // Including the deprecated "experimental" field should not break processing. - {name: "with legacy experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`}}, + // Invalid cases. + { + name: "empty path", + plugin: &fakeCandidate{path: ""}, + err: "plugin candidate path cannot be empty", + }, + { + name: "bad prefix", + plugin: &fakeCandidate{path: badPrefixPath}, + err: fmt.Sprintf("does not have %q prefix", metadata.NamePrefix), + }, + { + name: "bad path", + plugin: &fakeCandidate{path: badNamePath}, + invalid: "did not match", + }, + { + name: "builtin command", + plugin: &fakeCandidate{path: builtinName}, + invalid: `plugin "builtin" duplicates builtin command`, + }, + { + name: "builtin alias", + plugin: &fakeCandidate{path: builtinAlias}, + invalid: `plugin "alias" duplicates an alias of builtin command "builtin"`, + }, + { + name: "fetch failure", + plugin: &fakeCandidate{path: goodPluginPath, exec: false}, + invalid: fmt.Sprintf("failed to fetch metadata: faked a failure to exec %q", goodPluginPath), + }, + { + name: "metadata not json", + plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `xyzzy`}, + invalid: "invalid character", + }, + { + name: "empty schemaversion", + plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`}, + invalid: `plugin SchemaVersion "" is not valid`, + }, + { + name: "invalid schemaversion", + plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`}, + invalid: `plugin SchemaVersion "xyzzy" is not valid`, + }, + { + name: "no vendor", + plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`}, + invalid: "plugin metadata does not define a vendor", + }, + { + name: "empty vendor", + plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`}, + invalid: "plugin metadata does not define a vendor", + }, + + // Valid cases. + { + name: "valid", + plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`}, + }, + { + // Including the deprecated "experimental" field should not break processing. + name: "with legacy experimental", + plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`}, + }, } { t.Run(tc.name, func(t *testing.T) { - p, err := newPlugin(tc.c, fakeroot.Commands()) + p, err := newPlugin(tc.plugin, fakeroot.Commands()) switch { case tc.err != "": assert.ErrorContains(t, err, tc.err) From 1fb1577626364b275d6c172d82141f056048812f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 24 Sep 2025 13:02:05 +0200 Subject: [PATCH 155/193] cli-plugins/plugin: Run: allow customizing the CLI Currently, the plugin.Run command constructs the DockerCli using the default options, assuming plugins run with all the same options as the CLI itself; to customize the CLI there's a "Apply" option, but this means mutating the CLI after it's already constructed, which is not ideal. This patch adds a variadic ops argument to allow CLI plugins to pass custom options to use for the CLI, so that there's no need to mutate its config in most cases. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 271180043066ec1baaa91351a63f1854667171d4) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/plugin/plugin.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cli-plugins/plugin/plugin.go b/cli-plugins/plugin/plugin.go index 9f307d1d2ed1..6dca555a85bf 100644 --- a/cli-plugins/plugin/plugin.go +++ b/cli-plugins/plugin/plugin.go @@ -82,12 +82,13 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadat // Run is the top-level entry point to the CLI plugin framework. It should // be called from the plugin's "main()" function. It initializes a new -// [command.DockerCli] instance before calling makeCmd to construct the -// plugin command, then invokes the plugin command using [RunPlugin]. -func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) { +// [command.DockerCli] instance with the given options before calling +// makeCmd to construct the plugin command, then invokes the plugin command +// using [RunPlugin]. +func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata, ops ...command.CLIOption) { otel.SetErrorHandler(debug.OTELErrorHandler) - dockerCLI, err := command.NewDockerCli() + dockerCLI, err := command.NewDockerCli(ops...) if err != nil { _, _ = fmt.Fprintln(os.Stderr, err) os.Exit(1) From 2ed0d99accbccd06681ca09e77f0c410dd31559f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 24 Sep 2025 16:12:46 +0200 Subject: [PATCH 156/193] cli-plugins/manager: allow schema-versions <= 2.0.0 The CLI currently hard-codes the schema-version for CLI plugins to "0.1.0", which doesn't allow us to expand the schema for plugins. As there's many plugins that we shipped already, we can't break compatibility until we reach 2.0.0, but we can expand the schema with non-breaking changes. This patch makes the validation more permissive to allow new schema versions <= 2.0.0. Note that existing CLIs will still invalidate such versions, so we cannot update the version until such CLIs are no longer expected to be used, but this patch lays the ground-work to open that option. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit ec912e552435bf5e618aeb203a7c1dfc27154161) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/candidate_test.go | 26 ++++++++++++++++++++--- cli-plugins/manager/plugin.go | 30 +++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/cli-plugins/manager/candidate_test.go b/cli-plugins/manager/candidate_test.go index ee7ff788b554..a0dbb0dd7b56 100644 --- a/cli-plugins/manager/candidate_test.go +++ b/cli-plugins/manager/candidate_test.go @@ -55,6 +55,7 @@ func TestValidateCandidate(t *testing.T) { // Either err or invalid may be non-empty, but not both (both can be empty for a good plugin). err string invalid string + expVer string }{ // Invalid cases. { @@ -95,12 +96,17 @@ func TestValidateCandidate(t *testing.T) { { name: "empty schemaversion", plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`}, - invalid: `plugin SchemaVersion "" is not valid`, + invalid: `plugin SchemaVersion version cannot be empty`, }, { name: "invalid schemaversion", plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`}, - invalid: `plugin SchemaVersion "xyzzy" is not valid`, + invalid: `plugin SchemaVersion "xyzzy" has wrong format: must be ..`, + }, + { + name: "invalid schemaversion major", + plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "2.0.0"}`}, + invalid: `plugin SchemaVersion "2.0.0" is not supported: must be lower than 2.0.0`, }, { name: "no vendor", @@ -117,11 +123,25 @@ func TestValidateCandidate(t *testing.T) { { name: "valid", plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`}, + expVer: "0.1.0", }, { // Including the deprecated "experimental" field should not break processing. name: "with legacy experimental", plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`}, + expVer: "0.1.0", + }, + { + // note that this may not be supported by older CLIs + name: "new minor schema version", + plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.2.0", "Vendor": "e2e-testing"}`}, + expVer: "0.2.0", + }, + { + // note that this may not be supported by older CLIs + name: "new major schema version", + plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "1.0.0", "Vendor": "e2e-testing"}`}, + expVer: "1.0.0", }, } { t.Run(tc.name, func(t *testing.T) { @@ -136,7 +156,7 @@ func TestValidateCandidate(t *testing.T) { default: assert.NilError(t, err) assert.Equal(t, metadata.NamePrefix+p.Name, goodPluginName) - assert.Equal(t, p.SchemaVersion, "0.1.0") + assert.Equal(t, p.SchemaVersion, tc.expVer) assert.Equal(t, p.Vendor, "e2e-testing") } }) diff --git a/cli-plugins/manager/plugin.go b/cli-plugins/manager/plugin.go index 2caae39b0292..fd8936174cc3 100644 --- a/cli-plugins/manager/plugin.go +++ b/cli-plugins/manager/plugin.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "github.com/docker/cli/cli-plugins/metadata" @@ -118,8 +119,8 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) { p.Err = wrapAsPluginError(err, "invalid metadata") return p, nil } - if p.Metadata.SchemaVersion != "0.1.0" { - p.Err = newPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion) + if err := validateSchemaVersion(p.Metadata.SchemaVersion); err != nil { + p.Err = &pluginError{cause: err} return p, nil } if p.Metadata.Vendor == "" { @@ -129,6 +130,31 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) { return p, nil } +// validateSchemaVersion validates if the plugin's schemaVersion is supported. +// +// The current schema-version is "0.1.0", but we don't want to break compatibility +// until v2.0.0 of the schema version. Check for the major version to be < 2.0.0. +// +// Note that CLI versions before 28.4.1 may not support these versions as they were +// hard-coded to only accept "0.1.0". +func validateSchemaVersion(version string) error { + if version == "0.1.0" { + return nil + } + if version == "" { + return errors.New("plugin SchemaVersion version cannot be empty") + } + major, _, ok := strings.Cut(version, ".") + majorVersion, err := strconv.Atoi(major) + if !ok || err != nil { + return fmt.Errorf("plugin SchemaVersion %q has wrong format: must be ..", version) + } + if majorVersion > 1 { + return fmt.Errorf("plugin SchemaVersion %q is not supported: must be lower than 2.0.0", version) + } + return nil +} + // RunHook executes the plugin's hooks command // and returns its unprocessed output. func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte, error) { From 4de56bc72f26736871aef786fb9d141c059d319e Mon Sep 17 00:00:00 2001 From: Austin Vazquez Date: Wed, 24 Sep 2025 11:37:46 -0500 Subject: [PATCH 157/193] vendor: github.com/docker/docker v28.5.0-dev Signed-off-by: Austin Vazquez --- vendor.mod | 2 +- vendor.sum | 4 +-- .../github.com/docker/docker/api/swagger.yaml | 31 ------------------- vendor/modules.txt | 2 +- 4 files changed, 4 insertions(+), 35 deletions(-) diff --git a/vendor.mod b/vendor.mod index d3ea0b714356..92ba4c789099 100644 --- a/vendor.mod +++ b/vendor.mod @@ -16,7 +16,7 @@ require ( github.com/distribution/reference v0.6.0 github.com/docker/cli-docs-tool v0.10.0 github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.4.0+incompatible + github.com/docker/docker v28.4.1-0.20250924162609-d21856f25dbe+incompatible // 28.x branch (v28.5.0+incompatible not yet released) github.com/docker/docker-credential-helpers v0.9.3 github.com/docker/go-connections v0.6.0 github.com/docker/go-units v0.5.0 diff --git a/vendor.sum b/vendor.sum index c5303fbbfc68..7b02f62234cd 100644 --- a/vendor.sum +++ b/vendor.sum @@ -57,8 +57,8 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= -github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.4.1-0.20250924162609-d21856f25dbe+incompatible h1:6aisV+k7ZyODFCKg5HzMbw2fgp5ibtZSKfnUutplsDQ= +github.com/docker/docker v28.4.1-0.20250924162609-d21856f25dbe+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= diff --git a/vendor/github.com/docker/docker/api/swagger.yaml b/vendor/github.com/docker/docker/api/swagger.yaml index 1401fa715359..fd69add12799 100644 --- a/vendor/github.com/docker/docker/api/swagger.yaml +++ b/vendor/github.com/docker/docker/api/swagger.yaml @@ -1531,37 +1531,6 @@ definitions: items: type: "string" example: ["/bin/sh", "-c"] - # FIXME(thaJeztah): temporarily using a full example to remove some "omitempty" fields. Remove once the fields are removed. - example: - "User": "web:web" - "ExposedPorts": { - "80/tcp": {}, - "443/tcp": {} - } - "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"] - "Cmd": ["/bin/sh"] - "Healthcheck": { - "Test": ["string"], - "Interval": 0, - "Timeout": 0, - "Retries": 0, - "StartPeriod": 0, - "StartInterval": 0 - } - "ArgsEscaped": true - "Volumes": { - "/app/data": {}, - "/app/config": {} - } - "WorkingDir": "/public/" - "Entrypoint": [] - "OnBuild": [] - "Labels": { - "com.example.some-label": "some-value", - "com.example.some-other-label": "some-other-value" - } - "StopSignal": "SIGTERM" - "Shell": ["/bin/sh", "-c"] NetworkingConfig: description: | diff --git a/vendor/modules.txt b/vendor/modules.txt index 712d74a15b2d..31eae1e7652b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/uuid -# github.com/docker/docker v28.4.0+incompatible +# github.com/docker/docker v28.4.1-0.20250924162609-d21856f25dbe+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types From 985cee2de001fb24d97b29d527f406d8286eb184 Mon Sep 17 00:00:00 2001 From: Milas Bowman Date: Wed, 20 Sep 2023 14:36:55 -0400 Subject: [PATCH 158/193] cli/command: add WithUserAgent option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support to the `cli/command` package to accept a custom User Agent to pass to the underlying client. This is used as the `UpstreamClient` portion of the `User-Agent` when the Moby daemon makes requests. For example, pushing and pulling images with Compose might result in the registry seeing a `User-Agent` value of: ``` docker/24.0.7 go/go1.20.10 git-commit/311b9ff kernel/6.5.13-linuxkit os/linux arch/arm64 UpstreamClient(docker-cli-plugin-compose/v2.24.0) ``` Signed-off-by: Milas Bowman Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 048e931b422a6baa26d12f818bbb14c501164c09) Signed-off-by: Sebastiaan van Stijn Signed-off-by: Paweł Gronowski --- cli/command/cli.go | 13 ++++++++----- cli/command/cli_options.go | 11 +++++++++++ cli/command/cli_test.go | 23 +++++++++++++++++++++++ cli/command/manifest/annotate.go | 1 + 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/cli/command/cli.go b/cli/command/cli.go index c62d6be3c44b..2d5e3088b9f3 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -78,6 +78,7 @@ type DockerCli struct { dockerEndpoint docker.Endpoint contextStoreConfig *store.Config initTimeout time.Duration + userAgent string res telemetryResource // baseCtx is the base context used for internal operations. In the future @@ -313,10 +314,10 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile. if err != nil { return nil, errors.Wrap(err, "unable to resolve docker endpoint") } - return newAPIClientFromEndpoint(endpoint, configFile) + return newAPIClientFromEndpoint(endpoint, configFile, client.WithUserAgent(UserAgent())) } -func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile) (client.APIClient, error) { +func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile, extraOpts ...client.Opt) (client.APIClient, error) { opts, err := ep.ClientOpts() if err != nil { return nil, err @@ -331,7 +332,7 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF if withCustomHeaders != nil { opts = append(opts, withCustomHeaders) } - opts = append(opts, client.WithUserAgent(UserAgent())) + opts = append(opts, extraOpts...) return client.NewClientWithOpts(opts...) } @@ -552,7 +553,8 @@ func (cli *DockerCli) initialize() error { return } if cli.client == nil { - if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile); cli.initErr != nil { + ops := []client.Opt{client.WithUserAgent(cli.userAgent)} + if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil { return } } @@ -599,6 +601,7 @@ func NewDockerCli(ops ...CLIOption) (*DockerCli, error) { WithContentTrustFromEnv(), WithDefaultContextStoreConfig(), WithStandardStreams(), + WithUserAgent(UserAgent()), } ops = append(defaultOps, ops...) @@ -622,7 +625,7 @@ func getServerHost(hosts []string, defaultToTLS bool) (string, error) { } } -// UserAgent returns the user agent string used for making API requests +// UserAgent returns the default user agent string used for making API requests. func UserAgent() string { return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")" } diff --git a/cli/command/cli_options.go b/cli/command/cli_options.go index a8bdd88eb486..0318fb8c27ab 100644 --- a/cli/command/cli_options.go +++ b/cli/command/cli_options.go @@ -236,3 +236,14 @@ func withCustomHeadersFromEnv() (client.Opt, error) { // see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc) return client.WithHTTPHeaders(env), nil } + +// WithUserAgent configures the User-Agent string for cli HTTP requests. +func WithUserAgent(userAgent string) CLIOption { + return func(cli *DockerCli) error { + if userAgent == "" { + return errors.New("user agent cannot be blank") + } + cli.userAgent = userAgent + return nil + } +} diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index cc30aed92689..859d6865fa78 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -374,3 +374,26 @@ func TestSetGoDebug(t *testing.T) { assert.Equal(t, "val1,val2=1", os.Getenv("GODEBUG")) }) } + +func TestNewDockerCliWithCustomUserAgent(t *testing.T) { + var received string + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + received = r.UserAgent() + w.WriteHeader(http.StatusOK) + })) + defer ts.Close() + host := strings.Replace(ts.URL, "http://", "tcp://", 1) + opts := &flags.ClientOptions{Hosts: []string{host}} + + cli, err := NewDockerCli( + WithUserAgent("fake-agent/0.0.1"), + ) + assert.NilError(t, err) + cli.currentContext = DefaultContextName + cli.options = opts + cli.configFile = &configfile.ConfigFile{} + + _, err = cli.Client().Ping(context.Background()) + assert.NilError(t, err) + assert.DeepEqual(t, received, "fake-agent/0.0.1") +} diff --git a/cli/command/manifest/annotate.go b/cli/command/manifest/annotate.go index cf9f21944517..d832588453e1 100644 --- a/cli/command/manifest/annotate.go +++ b/cli/command/manifest/annotate.go @@ -54,6 +54,7 @@ func newRegistryClient(dockerCLI command.Cli, allowInsecure bool) registryclient resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig { return command.ResolveAuthConfig(dockerCLI.ConfigFile(), index) } + // FIXME(thaJeztah): this should use the userAgent as configured on the dockerCLI. return registryclient.NewRegistryClient(resolver, command.UserAgent(), allowInsecure) } From db5a0ae673abaac4b9f1faf4862e849c08031918 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 25 Sep 2025 09:44:07 +0200 Subject: [PATCH 159/193] e2e: update openssh, openssl to work around openssh bug relates to https://gitlab.alpinelinux.org/alpine/aports/-/issues/17547 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit b611f288eefa492b735ae65d12587f64c58cf10f) Signed-off-by: Sebastiaan van Stijn --- e2e/testdata/Dockerfile.connhelper-ssh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/testdata/Dockerfile.connhelper-ssh b/e2e/testdata/Dockerfile.connhelper-ssh index ccbe66da05a2..5fd05c3c858f 100644 --- a/e2e/testdata/Dockerfile.connhelper-ssh +++ b/e2e/testdata/Dockerfile.connhelper-ssh @@ -7,8 +7,8 @@ ARG ENGINE_VERSION=28 FROM docker:${ENGINE_VERSION}-dind # the openssh-client update is needed for security reasons when using docker:23.0-dind, currently maintained as an lts by mirantis -RUN apk --no-cache upgrade openssh-client && \ - apk --no-cache add shadow openssh-server && \ +RUN apk --no-cache add openssl openssh-client openssh-server shadow && \ + apk --no-cache upgrade openssl openssh-client openssh-server && \ # TODO(krissetto): `groupadd` can be removed once we only test against moby >= v24 # see https://github.com/docker-library/docker/pull/470 groupadd -f docker && \ From 261d8bcf8dcb04ffa9bb5a0a28ce1cfe52677e12 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 25 Sep 2025 10:30:35 +0200 Subject: [PATCH 160/193] trust: add internal utility for checking DOCKER_CONTENT_TRUST Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 1bae6aafa86629d2f840a9432999708e13c4f468) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/create.go | 2 +- cli/command/container/create_test.go | 3 ++- cli/command/container/run.go | 3 ++- cli/command/container/run_test.go | 3 ++- cli/command/image/pull.go | 2 +- cli/command/image/pull_test.go | 3 ++- cli/command/image/push.go | 3 ++- cli/command/service/trust.go | 2 +- cli/trust/trust.go | 15 +++++++++++++++ internal/test/cli.go | 11 ----------- 10 files changed, 28 insertions(+), 19 deletions(-) diff --git a/cli/command/container/create.go b/cli/command/container/create.go index f38746fd3cf6..05368ef98723 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -96,7 +96,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command { addPlatformFlag(flags, &options.platform) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) - flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification") + flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification") copts = addFlags(flags) addCompletions(cmd, dockerCLI) diff --git a/cli/command/container/create_test.go b/cli/command/container/create_test.go index 1b7add56375c..3e1379b161af 100644 --- a/cli/command/container/create_test.go +++ b/cli/command/container/create_test.go @@ -249,6 +249,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + t.Setenv("DOCKER_CONTENT_TRUST", "true") fakeCLI := test.NewFakeCli(&fakeClient{ createContainerFunc: func(config *container.Config, hostConfig *container.HostConfig, @@ -258,7 +259,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) { ) (container.CreateResponse, error) { return container.CreateResponse{}, errors.New("shouldn't try to pull image") }, - }, test.EnableContentTrust) + }) fakeCLI.SetNotaryClient(tc.notaryFunc) cmd := newCreateCommand(fakeCLI) cmd.SetOut(io.Discard) diff --git a/cli/command/container/run.go b/cli/command/container/run.go index 3cdb71c927d5..1b55400daf06 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -10,6 +10,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" + "github.com/docker/cli/cli/trust" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/container" "github.com/moby/sys/signal" @@ -74,7 +75,7 @@ func newRunCommand(dockerCLI command.Cli) *cobra.Command { // TODO(thaJeztah): consider adding platform as "image create option" on containerOptions addPlatformFlag(flags, &options.platform) - flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification") + flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification") copts = addFlags(flags) _ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys) diff --git a/cli/command/container/run_test.go b/cli/command/container/run_test.go index f75fe7ebefde..332c6a493f7e 100644 --- a/cli/command/container/run_test.go +++ b/cli/command/container/run_test.go @@ -323,6 +323,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + t.Setenv("DOCKER_CONTENT_TRUST", "true") fakeCLI := test.NewFakeCli(&fakeClient{ createContainerFunc: func(config *container.Config, hostConfig *container.HostConfig, @@ -332,7 +333,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) { ) (container.CreateResponse, error) { return container.CreateResponse{}, errors.New("shouldn't try to pull image") }, - }, test.EnableContentTrust) + }) fakeCLI.SetNotaryClient(tc.notaryFunc) cmd := newRunCommand(fakeCLI) cmd.SetArgs(tc.args) diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index 2f010fb6f139..a45e0b7435f5 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -56,7 +56,7 @@ func newPullCommand(dockerCLI command.Cli) *cobra.Command { flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output") addPlatformFlag(flags, &opts.platform) - flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification") + flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification") _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) diff --git a/cli/command/image/pull_test.go b/cli/command/image/pull_test.go index ba7ff062133d..baf375fdfeed 100644 --- a/cli/command/image/pull_test.go +++ b/cli/command/image/pull_test.go @@ -118,11 +118,12 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + t.Setenv("DOCKER_CONTENT_TRUST", "true") cli := test.NewFakeCli(&fakeClient{ imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) { return io.NopCloser(strings.NewReader("")), errors.New("shouldn't try to pull image") }, - }, test.EnableContentTrust) + }) cli.SetNotaryClient(tc.notaryFunc) cmd := newPullCommand(cli) cmd.SetOut(io.Discard) diff --git a/cli/command/image/push.go b/cli/command/image/push.go index 480ee0d31b64..5da2f3e46963 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -15,6 +15,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/streams" + "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/jsonstream" "github.com/docker/cli/internal/registry" "github.com/docker/cli/internal/tui" @@ -64,7 +65,7 @@ func newPushCommand(dockerCLI command.Cli) *cobra.Command { flags := cmd.Flags() flags.BoolVarP(&opts.all, "all-tags", "a", false, "Push all tags of an image to the repository") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output") - flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image signing") + flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image signing") // Don't default to DOCKER_DEFAULT_PLATFORM env variable, always default to // pushing the image as-is. This also avoids forcing the platform selection diff --git a/cli/command/service/trust.go b/cli/command/service/trust.go index ea79161ad55f..fe4b08c67b8a 100644 --- a/cli/command/service/trust.go +++ b/cli/command/service/trust.go @@ -15,7 +15,7 @@ import ( ) func resolveServiceImageDigestContentTrust(dockerCli command.Cli, service *swarm.ServiceSpec) error { - if !dockerCli.ContentTrustEnabled() { + if !trust.Enabled() { // When not using content trust, digest resolution happens later when // contacting the registry to retrieve image information. return nil diff --git a/cli/trust/trust.go b/cli/trust/trust.go index eeddc912b331..0e9be5d36412 100644 --- a/cli/trust/trust.go +++ b/cli/trust/trust.go @@ -10,6 +10,7 @@ import ( "os" "path" "path/filepath" + "strconv" "time" "github.com/distribution/reference" @@ -42,6 +43,20 @@ var ( ActionsPushAndPull = []string{"pull", "push"} ) +// Enabled returns whether content-trust is enabled through the DOCKER_CONTENT_TRUST env-var. +// +// IMPORTANT: this function is for internal use, and may be removed at any moment. +func Enabled() bool { + var enabled bool + if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" { + if t, err := strconv.ParseBool(e); t || err != nil { + // treat any other value as true + enabled = true + } + } + return enabled +} + // NotaryServer is the endpoint serving the Notary trust server const NotaryServer = "https://notary.docker.io" diff --git a/internal/test/cli.go b/internal/test/cli.go index 5a2d74b678dc..72591bac1baf 100644 --- a/internal/test/cli.go +++ b/internal/test/cli.go @@ -36,7 +36,6 @@ type FakeCli struct { notaryClientFunc NotaryClientFuncType manifestStore manifeststore.Store registryClient registryclient.RegistryClient - contentTrust bool contextStore store.Store currentContext string dockerEndpoint docker.Endpoint @@ -198,16 +197,6 @@ func (c *FakeCli) SetRegistryClient(registryClient registryclient.RegistryClient c.registryClient = registryClient } -// ContentTrustEnabled on the fake cli -func (c *FakeCli) ContentTrustEnabled() bool { - return c.contentTrust -} - -// EnableContentTrust on the fake cli -func EnableContentTrust(c *FakeCli) { - c.contentTrust = true -} - // BuildKitEnabled on the fake cli func (*FakeCli) BuildKitEnabled() (bool, error) { return true, nil From 466509171545c7eecada0bba7a2358f9cf19f6a5 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 25 Sep 2025 09:17:01 +0200 Subject: [PATCH 161/193] cli/command: deprecate WithContentTrustFromEnv, WithContentTrust These options were used internally as defaults for the constructor and only impact commands implemented in the CLI itself. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 40cdfc0d8133de2b168784d06aa5720fe0cd9c42) Signed-off-by: Sebastiaan van Stijn --- cli/command/cli.go | 2 +- cli/command/cli_options.go | 13 +++++++++++-- cli/command/cli_options_test.go | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/cli/command/cli.go b/cli/command/cli.go index 2d5e3088b9f3..9bc8130458d7 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -598,7 +598,7 @@ type ServerInfo struct { // environment. func NewDockerCli(ops ...CLIOption) (*DockerCli, error) { defaultOps := []CLIOption{ - WithContentTrustFromEnv(), + withContentTrustFromEnv(), WithDefaultContextStoreConfig(), WithStandardStreams(), WithUserAgent(UserAgent()), diff --git a/cli/command/cli_options.go b/cli/command/cli_options.go index 0318fb8c27ab..6af65e98e376 100644 --- a/cli/command/cli_options.go +++ b/cli/command/cli_options.go @@ -75,8 +75,8 @@ func WithErrorStream(err io.Writer) CLIOption { } } -// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value. -func WithContentTrustFromEnv() CLIOption { +// withContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value. +func withContentTrustFromEnv() CLIOption { return func(cli *DockerCli) error { cli.contentTrust = false if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" { @@ -89,7 +89,16 @@ func WithContentTrustFromEnv() CLIOption { } } +// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value. +// +// Deprecated: this option is no longer used, and will be removed in the next release. +func WithContentTrustFromEnv() CLIOption { + return withContentTrustFromEnv() +} + // WithContentTrust enables content trust on a cli. +// +// Deprecated: this option is no longer used, and will be removed in the next release. func WithContentTrust(enabled bool) CLIOption { return func(cli *DockerCli) error { cli.contentTrust = enabled diff --git a/cli/command/cli_options_test.go b/cli/command/cli_options_test.go index 45ac1d8a5773..946a94df7468 100644 --- a/cli/command/cli_options_test.go +++ b/cli/command/cli_options_test.go @@ -10,7 +10,7 @@ import ( func contentTrustEnabled(t *testing.T) bool { t.Helper() var cli DockerCli - assert.NilError(t, WithContentTrustFromEnv()(&cli)) + assert.NilError(t, withContentTrustFromEnv()(&cli)) return cli.contentTrust } From 026ef0df2da69f72433b50097684a0af73502209 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 25 Sep 2025 10:46:07 +0200 Subject: [PATCH 162/193] [28.x] remove remaining uses of DockerCli.ContentTrustEnabled These were already replaced for stubs in master. Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build.go | 2 +- cli/command/plugin/install.go | 7 ++++--- cli/command/plugin/install_test.go | 3 ++- cli/command/plugin/push.go | 2 +- cli/command/plugin/upgrade.go | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cli/command/image/build.go b/cli/command/image/build.go index 4d54cae2f7d0..a8d2c4ae2e67 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -151,7 +151,7 @@ func newBuildCommand(dockerCli command.Cli) *cobra.Command { flags.SetAnnotation("target", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/buildx/build/#target"}) flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file") - flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") + flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification") flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") flags.SetAnnotation("platform", "version", []string{"1.38"}) diff --git a/cli/command/plugin/install.go b/cli/command/plugin/install.go index 715ae76ae107..21bbf63aebd6 100644 --- a/cli/command/plugin/install.go +++ b/cli/command/plugin/install.go @@ -8,6 +8,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/image" + "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/jsonstream" "github.com/docker/cli/internal/prompt" "github.com/docker/cli/internal/registry" @@ -28,9 +29,9 @@ type pluginOptions struct { untrusted bool } -func loadPullFlags(dockerCli command.Cli, opts *pluginOptions, flags *pflag.FlagSet) { +func loadPullFlags(opts *pluginOptions, flags *pflag.FlagSet) { flags.BoolVar(&opts.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin") - flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification") + flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification") } func newInstallCommand(dockerCli command.Cli) *cobra.Command { @@ -49,7 +50,7 @@ func newInstallCommand(dockerCli command.Cli) *cobra.Command { } flags := cmd.Flags() - loadPullFlags(dockerCli, &options, flags) + loadPullFlags(&options, flags) flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install") flags.StringVar(&options.localName, "alias", "", "Local name for plugin") return cmd diff --git a/cli/command/plugin/install_test.go b/cli/command/plugin/install_test.go index ee67e611d7dd..f534f0063a11 100644 --- a/cli/command/plugin/install_test.go +++ b/cli/command/plugin/install_test.go @@ -86,11 +86,12 @@ func TestInstallContentTrustErrors(t *testing.T) { for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { + t.Setenv("DOCKER_CONTENT_TRUST", "true") cli := test.NewFakeCli(&fakeClient{ pluginInstallFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) { return nil, errors.New("should not try to install plugin") }, - }, test.EnableContentTrust) + }) cli.SetNotaryClient(tc.notaryFunc) cmd := newInstallCommand(cli) cmd.SetArgs(tc.args) diff --git a/cli/command/plugin/push.go b/cli/command/plugin/push.go index 6598363f0b50..b4db95bb0f03 100644 --- a/cli/command/plugin/push.go +++ b/cli/command/plugin/push.go @@ -33,7 +33,7 @@ func newPushCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() - flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image signing") + flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image signing") return cmd } diff --git a/cli/command/plugin/upgrade.go b/cli/command/plugin/upgrade.go index 54a39685b702..4ebf003e39fd 100644 --- a/cli/command/plugin/upgrade.go +++ b/cli/command/plugin/upgrade.go @@ -30,7 +30,7 @@ func newUpgradeCommand(dockerCli command.Cli) *cobra.Command { } flags := cmd.Flags() - loadPullFlags(dockerCli, &options, flags) + loadPullFlags(&options, flags) flags.BoolVar(&options.skipRemoteCheck, "skip-remote-check", false, "Do not check if specified remote plugin matches existing plugin image") return cmd } From 118548d02b91ecd0700745c3159014e62837af6c Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 25 Sep 2025 09:54:40 +0200 Subject: [PATCH 163/193] cli/command: deprecate DockerCli.ContentTrustEnabled This function was used internally, but is no longer used. Users should check the value of the `DOCKER_CONTENT_TRUST` environment variable instead. There are no known external users of this method, so already removing it from the Cli interface; this method will be removed in the next release. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 11d40488dd818aadbb4757ff57da040479b4e9a6) Signed-off-by: Sebastiaan van Stijn --- cli/command/cli.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/command/cli.go b/cli/command/cli.go index 2d5e3088b9f3..ab7ec35f64d5 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -50,7 +50,6 @@ type Cli interface { ServerInfo() ServerInfo DefaultVersion() string CurrentVersion() string - ContentTrustEnabled() bool BuildKitEnabled() (bool, error) ContextStore() store.Store CurrentContext() string @@ -160,6 +159,8 @@ func (cli *DockerCli) ServerInfo() ServerInfo { // ContentTrustEnabled returns whether content trust has been enabled by an // environment variable. +// +// Deprecated: check the value of the DOCKER_CONTENT_TRUST environment variable to detect whether content-trust is enabled. func (cli *DockerCli) ContentTrustEnabled() bool { return cli.contentTrust } From e9c189e1c26a071e84a675dfda4a6e2f7a4ec518 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 25 Sep 2025 09:29:21 +0200 Subject: [PATCH 164/193] cli/command: deprecate DockerCli.DefaultVersion This function was used internally, but is no longer used. There are no known users of this method, so already removing it from the Cli interface. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 0270b2d6f764f4d564deba1ac504527bdcf65da1) Signed-off-by: Sebastiaan van Stijn --- cli/command/cli.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/command/cli.go b/cli/command/cli.go index 2d5e3088b9f3..2984cc5c4eed 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -48,7 +48,6 @@ type Cli interface { Apply(ops ...CLIOption) error config.Provider ServerInfo() ServerInfo - DefaultVersion() string CurrentVersion() string ContentTrustEnabled() bool BuildKitEnabled() (bool, error) @@ -90,6 +89,8 @@ type DockerCli struct { } // DefaultVersion returns [api.DefaultVersion]. +// +// Deprecated: this function is no longer used and will be removed in the next release. func (*DockerCli) DefaultVersion() string { return api.DefaultVersion } From d1c83363957eea186875d8d950d655c3bff30625 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 25 Sep 2025 13:27:28 +0200 Subject: [PATCH 165/193] cli/command: deprecate DockerCli.Apply The Apply method was added when CLI options for constructing the CLI were rewritten into functional options in [cli@7f207f3]. There was no mention in the pull request of this method specifically, and this may have been related to work being done elsewhere on compose-on-kubernetes or the compose-cli plugin that may have needed options to modify the CLI config after it was already initialized. The CLI itself no longer depends on this method since [cli@133279f], and the only known consumer (docker compose) no longer needs it since [cli@2711800] and [cli@048e931]. This patch deprecates the method with the intent to remove it in a future release. [cli@7f207f3]: https://github.com/docker/cli/commit/7f207f3f957ed3f5129aeb22bef2a429c14caf22 [cli@133279f]: https://github.com/docker/cli/commit/133279fb0d4adea30d27d27eb8789b79405fc82b [cli@2711800]: https://github.com/docker/cli/commit/271180043066ec1baaa91351a63f1854667171d4 [cli@048e931]: https://github.com/docker/cli/commit/048e931b422a6baa26d12f818bbb14c501164c09 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 24bfedf3f88762bcd46e6452d6547d838f780e6b) Signed-off-by: Sebastiaan van Stijn --- cli/command/cli.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/command/cli.go b/cli/command/cli.go index 2d5e3088b9f3..f418af55a0ae 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -567,6 +567,8 @@ func (cli *DockerCli) initialize() error { } // Apply all the operation on the cli +// +// Deprecated: this method is no longer used and will be removed in the next release if there are no remaining users. func (cli *DockerCli) Apply(ops ...CLIOption) error { for _, op := range ops { if err := op(cli); err != nil { From f9d2820a2060b848fe2bf45adb30c1cd556bc22b Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 25 Sep 2025 15:50:30 +0200 Subject: [PATCH 166/193] vendor: github.com/docker/docker v28.5.0-rc.1 full diff: https://github.com/docker/docker/compare/d21856f25dbe...v28.5.0-rc.1 Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 2 +- vendor.sum | 4 ++-- .../github.com/docker/docker/api/swagger.yaml | 23 ------------------- vendor/modules.txt | 2 +- 4 files changed, 4 insertions(+), 27 deletions(-) diff --git a/vendor.mod b/vendor.mod index 92ba4c789099..4b47f351825c 100644 --- a/vendor.mod +++ b/vendor.mod @@ -16,7 +16,7 @@ require ( github.com/distribution/reference v0.6.0 github.com/docker/cli-docs-tool v0.10.0 github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.4.1-0.20250924162609-d21856f25dbe+incompatible // 28.x branch (v28.5.0+incompatible not yet released) + github.com/docker/docker v28.5.0-rc.1+incompatible github.com/docker/docker-credential-helpers v0.9.3 github.com/docker/go-connections v0.6.0 github.com/docker/go-units v0.5.0 diff --git a/vendor.sum b/vendor.sum index 7b02f62234cd..4bddc6744b03 100644 --- a/vendor.sum +++ b/vendor.sum @@ -57,8 +57,8 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.4.1-0.20250924162609-d21856f25dbe+incompatible h1:6aisV+k7ZyODFCKg5HzMbw2fgp5ibtZSKfnUutplsDQ= -github.com/docker/docker v28.4.1-0.20250924162609-d21856f25dbe+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.5.0-rc.1+incompatible h1:cKKJLCI3dTn6olzoIUVJtQ2QVOVtPMUkl0M9U/LMZI4= +github.com/docker/docker v28.5.0-rc.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= diff --git a/vendor/github.com/docker/docker/api/swagger.yaml b/vendor/github.com/docker/docker/api/swagger.yaml index fd69add12799..e8e3f610c7c8 100644 --- a/vendor/github.com/docker/docker/api/swagger.yaml +++ b/vendor/github.com/docker/docker/api/swagger.yaml @@ -6388,29 +6388,6 @@ definitions: description: "Indicates IPv4 forwarding is enabled." type: "boolean" example: true - BridgeNfIptables: - description: | - Indicates if `bridge-nf-call-iptables` is available on the host when - the daemon was started. - -


- - > **Deprecated**: netfilter module is now loaded on-demand and no longer - > during daemon startup, making this field obsolete. This field is always - > `false` and will be removed in a API v1.49. - type: "boolean" - example: false - BridgeNfIp6tables: - description: | - Indicates if `bridge-nf-call-ip6tables` is available on the host. - -


- - > **Deprecated**: netfilter module is now loaded on-demand, and no longer - > during daemon startup, making this field obsolete. This field is always - > `false` and will be removed in a API v1.49. - type: "boolean" - example: false Debug: description: | Indicates if the daemon is running in debug-mode / with debug-level diff --git a/vendor/modules.txt b/vendor/modules.txt index 31eae1e7652b..8bfe39166d26 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/uuid -# github.com/docker/docker v28.4.1-0.20250924162609-d21856f25dbe+incompatible +# github.com/docker/docker v28.5.0-rc.1+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types From ec00b857941e6899c68e94714ace456578f0da4f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sat, 23 Aug 2025 16:18:09 +0200 Subject: [PATCH 167/193] cli/command/image: runPush: minor cleanups and linting issues - Remove redundant intermediate variables - Explicitly use an early return on error instead of combining with other checks. - Fix unhandled errors and combine defers - Remove outstanding TODO that unlikely will be addressed Signed-off-by: Sebastiaan van Stijn (cherry picked from commit c36e67d7b68601870936c9b0f7285a7f7c8544f3) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/push.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cli/command/image/push.go b/cli/command/image/push.go index 5da2f3e46963..950846df857d 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -100,9 +100,11 @@ To push the complete multi-platform image, remove the --platform flag. } ref, err := reference.ParseNormalizedNamed(opts.remote) - switch { - case err != nil: + if err != nil { return err + } + + switch { case opts.all && !reference.IsNameOnly(ref): return errors.New("tag can't be used with --all-tags/-a") case !opts.all && reference.IsNameOnly(ref): @@ -121,34 +123,32 @@ To push the complete multi-platform image, remove the --platform flag. if err != nil { return err } - options := image.PushOptions{ + + responseBody, err := dockerCli.Client().ImagePush(ctx, reference.FamiliarString(ref), image.PushOptions{ All: opts.all, RegistryAuth: encodedAuth, PrivilegeFunc: nil, Platform: platform, - } - - responseBody, err := dockerCli.Client().ImagePush(ctx, reference.FamiliarString(ref), options) + }) if err != nil { return err } defer func() { + _ = responseBody.Close() for _, note := range notes { out.PrintNote(note) } }() - defer responseBody.Close() if !opts.untrusted { - // TODO pushTrustedReference currently doesn't respect `--quiet` return pushTrustedReference(ctx, dockerCli, indexInfo, ref, authConfig, responseBody) } if opts.quiet { err = jsonstream.Display(ctx, responseBody, streams.NewOut(io.Discard), jsonstream.WithAuxCallback(handleAux())) if err == nil { - fmt.Fprintln(dockerCli.Out(), ref.String()) + _, _ = fmt.Fprintln(dockerCli.Out(), ref.String()) } return err } From 0351ece9e55713905f4fffca43a89932d6ed3f5d Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 26 Sep 2025 10:50:42 +0200 Subject: [PATCH 168/193] trust: print deprecation warning when using hub Notary server Docker Hub's Notary service is being retired, and now produces failures in most cases. Add a warning when attempting to use it, pending full removal of trust; https://www.docker.com/blog/retiring-docker-content-trust/ With this PR: DOCKER_CONTENT_TRUST=1 docker pull -q hello-world WARNING: Docker is retiring DCT for Docker Official Images (DOI). For details, refer to https://docs.docker.com/go/dct-deprecation/ could not validate the path to a trusted root: unable to retrieve valid leaf certificates Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 43b03ef2c58562e8175a86805659610fcf94bbce) Signed-off-by: Sebastiaan van Stijn --- cli/trust/trust.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cli/trust/trust.go b/cli/trust/trust.go index 0e9be5d36412..edfaa15830e4 100644 --- a/cli/trust/trust.go +++ b/cli/trust/trust.go @@ -3,6 +3,7 @@ package trust import ( "context" "encoding/json" + "fmt" "io" "net" "net/http" @@ -107,6 +108,11 @@ func (scs simpleCredentialStore) RefreshToken(*url.URL, string) string { func (simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {} +const dctDeprecation = `WARNING: Docker is retiring DCT for Docker Official Images (DOI). + For details, refer to https://docs.docker.com/go/dct-deprecation/ + +` + // GetNotaryRepository returns a NotaryRepository which stores all the // information needed to operate on a notary repository. // It creates an HTTP transport providing authentication support. @@ -115,6 +121,9 @@ func GetNotaryRepository(in io.Reader, out io.Writer, userAgent string, repoInfo if err != nil { return nil, err } + if server == NotaryServer { + _, _ = fmt.Fprint(os.Stderr, dctDeprecation) + } cfg := tlsconfig.ClientDefault() cfg.InsecureSkipVerify = !repoInfo.Index.Secure From d5c181abf4e2eb732f8a096fa4ff8c1b00a2435c Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 8 Sep 2025 17:40:39 +0200 Subject: [PATCH 169/193] cli/command/image: pushTrustedReference: internalize constructing indexInfo All information needed can be deducted from the image reference, which is used to create a indexInfo, repoInfo, and to resolve auth-config. In some situations this may result in resolving the auth-config twice after it already was resolved to an encoded auth-config. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 9a6313ed3bddb3721916cccb15efcc037e8cad82) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/push.go | 10 ++-------- cli/command/image/trust.go | 11 +++++++++-- cli/trust/trust.go | 5 ++++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/cli/command/image/push.go b/cli/command/image/push.go index 950846df857d..db19e8fb4c2b 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -17,11 +17,9 @@ import ( "github.com/docker/cli/cli/streams" "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/jsonstream" - "github.com/docker/cli/internal/registry" "github.com/docker/cli/internal/tui" "github.com/docker/docker/api/types/auxprogress" "github.com/docker/docker/api/types/image" - registrytypes "github.com/docker/docker/api/types/registry" "github.com/morikuni/aec" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -114,12 +112,8 @@ To push the complete multi-platform image, remove the --platform flag. } } - // Resolve the Repository name from fqn to RepositoryInfo - indexInfo := registry.NewIndexInfo(ref) - // Resolve the Auth config relevant for this server - authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), indexInfo) - encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig) + encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), ref.String()) if err != nil { return err } @@ -142,7 +136,7 @@ To push the complete multi-platform image, remove the --platform flag. }() if !opts.untrusted { - return pushTrustedReference(ctx, dockerCli, indexInfo, ref, authConfig, responseBody) + return pushTrustedReference(ctx, dockerCli, ref, responseBody) } if opts.quiet { diff --git a/cli/command/image/trust.go b/cli/command/image/trust.go index 58c69a9bf719..cb17505592a4 100644 --- a/cli/command/image/trust.go +++ b/cli/command/image/trust.go @@ -12,6 +12,7 @@ import ( "github.com/docker/cli/cli/streams" "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/jsonstream" + "github.com/docker/cli/internal/registry" "github.com/docker/docker/api/types/image" registrytypes "github.com/docker/docker/api/types/registry" "github.com/opencontainers/go-digest" @@ -42,12 +43,18 @@ func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth) ( } // pushTrustedReference pushes a canonical reference to the trust server. -func pushTrustedReference(ctx context.Context, ioStreams command.Streams, indexInfo *registrytypes.IndexInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader) error { +func pushTrustedReference(ctx context.Context, dockerCLI command.Cli, ref reference.Named, responseBody io.Reader) error { + // Resolve the Repository name from fqn to RepositoryInfo, and create an + // IndexInfo. Docker Content Trust uses the IndexInfo.Official field to + // select the right domain for Docker Hub's Notary server; + // https://github.com/docker/cli/blob/v28.4.0/cli/trust/trust.go#L65-L79 + indexInfo := registry.NewIndexInfo(ref) repoInfo := &trust.RepositoryInfo{ Name: reference.TrimNamed(ref), Index: indexInfo, } - return trust.PushTrustedReference(ctx, ioStreams, repoInfo, ref, authConfig, in, command.UserAgent()) + authConfig := command.ResolveAuthConfig(dockerCLI.ConfigFile(), indexInfo) + return trust.PushTrustedReference(ctx, dockerCLI, repoInfo, ref, authConfig, responseBody, command.UserAgent()) } // trustedPull handles content trust pulling of an image diff --git a/cli/trust/trust.go b/cli/trust/trust.go index 0e9be5d36412..a48cfc7b8391 100644 --- a/cli/trust/trust.go +++ b/cli/trust/trust.go @@ -342,7 +342,10 @@ func GetImageReferencesAndAuth(ctx context.Context, return ImageRefAndAuth{}, err } - // Resolve the Repository name from fqn to RepositoryInfo + // Resolve the Repository name from fqn to RepositoryInfo, and create an + // IndexInfo. Docker Content Trust uses the IndexInfo.Official field to + // select the right domain for Docker Hub's Notary server; + // https://github.com/docker/cli/blob/v28.4.0/cli/trust/trust.go#L65-L79 indexInfo := registry.NewIndexInfo(ref) authConfig := authResolver(ctx, indexInfo) return ImageRefAndAuth{ From a4ae5f2f7ac27e2beb46590c2a951edbbecdc516 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 26 Sep 2025 23:30:57 +0200 Subject: [PATCH 170/193] cli/manifest/store: deprecate IsNotFound Deprecate the IsNotFound utility in favor of errdefs. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit f3fb7728c7e03909268fa2c621c6febcaca138a0) Signed-off-by: Sebastiaan van Stijn --- cli/command/manifest/annotate.go | 3 ++- cli/command/manifest/create_list.go | 4 ++-- cli/command/manifest/util.go | 4 ++-- cli/manifest/store/store.go | 25 ++++++------------------- cli/manifest/store/store_test.go | 5 +++-- 5 files changed, 15 insertions(+), 26 deletions(-) diff --git a/cli/command/manifest/annotate.go b/cli/command/manifest/annotate.go index d832588453e1..6fc898203253 100644 --- a/cli/command/manifest/annotate.go +++ b/cli/command/manifest/annotate.go @@ -5,6 +5,7 @@ import ( "fmt" "path/filepath" + "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config" @@ -97,7 +98,7 @@ func runManifestAnnotate(dockerCLI command.Cli, opts annotateOptions) error { manifestStore := newManifestStore(dockerCLI) imageManifest, err := manifestStore.Get(targetRef, imgRef) switch { - case store.IsNotFound(err): + case errdefs.IsNotFound(err): return fmt.Errorf("manifest for image %s does not exist in %s", opts.image, opts.target) case err != nil: return err diff --git a/cli/command/manifest/create_list.go b/cli/command/manifest/create_list.go index 58c18124dfce..71483c0fa80d 100644 --- a/cli/command/manifest/create_list.go +++ b/cli/command/manifest/create_list.go @@ -4,9 +4,9 @@ import ( "context" "fmt" + "github.com/containerd/errdefs" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/manifest/store" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -44,7 +44,7 @@ func createManifestList(ctx context.Context, dockerCLI command.Cli, args []strin manifestStore := newManifestStore(dockerCLI) _, err = manifestStore.GetList(targetRef) switch { - case store.IsNotFound(err): + case errdefs.IsNotFound(err): // New manifest list case err != nil: return err diff --git a/cli/command/manifest/util.go b/cli/command/manifest/util.go index be9fe9ea1ba4..c10a98b0d8f4 100644 --- a/cli/command/manifest/util.go +++ b/cli/command/manifest/util.go @@ -3,9 +3,9 @@ package manifest import ( "context" + "github.com/containerd/errdefs" "github.com/distribution/reference" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/manifest/store" "github.com/docker/cli/cli/manifest/types" ) @@ -72,7 +72,7 @@ func normalizeReference(ref string) (reference.Named, error) { func getManifest(ctx context.Context, dockerCLI command.Cli, listRef, namedRef reference.Named, insecure bool) (types.ImageManifest, error) { data, err := newManifestStore(dockerCLI).Get(listRef, namedRef) switch { - case store.IsNotFound(err): + case errdefs.IsNotFound(err): return newRegistryClient(dockerCLI, insecure).GetManifest(ctx, namedRef) case err != nil: return types.ImageManifest{}, err diff --git a/cli/manifest/store/store.go b/cli/manifest/store/store.go index e97e8628f125..7518b5d4a1dc 100644 --- a/cli/manifest/store/store.go +++ b/cli/manifest/store/store.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "github.com/containerd/errdefs" "github.com/distribution/reference" "github.com/docker/cli/cli/manifest/types" "github.com/docker/distribution/manifest/manifestlist" @@ -152,27 +153,13 @@ func makeFilesafeName(ref string) string { return strings.ReplaceAll(fileName, "/", "_") } -type notFoundError struct { - object string +func newNotFoundError(ref string) error { + return errdefs.ErrNotFound.WithMessage("No such manifest: " + ref) } -func newNotFoundError(ref string) *notFoundError { - return ¬FoundError{object: ref} -} - -func (n *notFoundError) Error() string { - return "No such manifest: " + n.object -} - -// NotFound interface -func (*notFoundError) NotFound() {} - // IsNotFound returns true if the error is a not found error +// +// Deprecated: use [errdefs.IsNotFound]. This function will be removed in the next release. func IsNotFound(err error) bool { - _, ok := err.(notFound) - return ok -} - -type notFound interface { - NotFound() + return errdefs.IsNotFound(err) } diff --git a/cli/manifest/store/store_test.go b/cli/manifest/store/store_test.go index 2b3a57819f8c..d5c3ef67818e 100644 --- a/cli/manifest/store/store_test.go +++ b/cli/manifest/store/store_test.go @@ -4,6 +4,7 @@ import ( "os" "testing" + "github.com/containerd/errdefs" "github.com/distribution/reference" "github.com/docker/cli/cli/manifest/types" "github.com/google/go-cmp/cmp" @@ -86,7 +87,7 @@ func TestStoreSaveAndGet(t *testing.T) { actual, err := store.Get(tc.listRef, tc.manifestRef) if tc.expectedErr != "" { assert.Error(t, err, tc.expectedErr) - assert.Check(t, IsNotFound(err)) + assert.Check(t, errdefs.IsNotFound(err)) return } assert.NilError(t, err) @@ -117,5 +118,5 @@ func TestStoreGetListDoesNotExist(t *testing.T) { listRef := ref("list") _, err := store.GetList(listRef) assert.Error(t, err, "No such manifest: list") - assert.Check(t, IsNotFound(err)) + assert.Check(t, errdefs.IsNotFound(err)) } From a3ffb8a148b37616a568c02fee5bd6f4121f40b3 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 29 Sep 2025 13:05:08 +0200 Subject: [PATCH 171/193] cli/config/memorystore: remove unused IsErrValueNotFound This utility was added in 9b83d5bbf9d6d7f8abf485609e99bc0cd3300dc0, but was never used. Remove the utility, and rewrite the error returned to implement the errdefs.NotFound interface, so that it can be detected using the errdefs.IsNotFound() utility if needed. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 3c78ac2aad565bdbd2ec74fb40133be355ba575b) Signed-off-by: Sebastiaan van Stijn --- cli/config/memorystore/store.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cli/config/memorystore/store.go b/cli/config/memorystore/store.go index 199083464ed8..267e1e3437bd 100644 --- a/cli/config/memorystore/store.go +++ b/cli/config/memorystore/store.go @@ -3,7 +3,6 @@ package memorystore import ( - "errors" "fmt" "maps" "os" @@ -13,12 +12,17 @@ import ( "github.com/docker/cli/cli/config/types" ) -var errValueNotFound = errors.New("value not found") +// notFoundErr is the error returned when a plugin could not be found. +type notFoundErr string -func IsErrValueNotFound(err error) bool { - return errors.Is(err, errValueNotFound) +func (notFoundErr) NotFound() {} + +func (e notFoundErr) Error() string { + return string(e) } +var errValueNotFound notFoundErr = "value not found" + type Config struct { lock sync.RWMutex memoryCredentials map[string]types.AuthConfig From e491078fc69ea21a5482193f2c93c6d660aa7467 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 29 Sep 2025 11:54:28 +0200 Subject: [PATCH 172/193] cli/command: explicitly map AuthConfig fields instead of a direct cast Commit [cli@27b2797] forked the AuthConfig type from the API, and changed existing code to do a direct cast / convert of the forked type to the API type. This can cause issues if the API types diverges, such as the removal of the Email field. This patch explicitly maps each field to the corresponding API type, but adds some TODOs, because various code-paths only included a subset of the fields, which may be intentional for fields that were meant to be handled on the daemon / registry-client only. We should evaluate these conversions to make sure these fields should be sent from the client or not (and possibly even removed from the API type). [cli@27b2797]: https://github.com/docker/cli/commit/27b2797f7deb3ca5b7f80371d825113deb1faca1 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 9f02d9643de89954f87bd99a978c7273c0a7c1ea) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build.go | 13 +++++++++-- cli/command/registry.go | 40 ++++++++++++++++++++++++++++------ cli/command/registry/login.go | 33 +++++++++++++++++++++++++--- cli/command/registry/search.go | 11 +++++++++- cli/command/registry_test.go | 13 +++++++++-- 5 files changed, 95 insertions(+), 15 deletions(-) diff --git a/cli/command/image/build.go b/cli/command/image/build.go index a8d2c4ae2e67..c0b002390cb7 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -340,8 +340,17 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) configFile := dockerCli.ConfigFile() creds, _ := configFile.GetAllCredentials() authConfigs := make(map[string]registrytypes.AuthConfig, len(creds)) - for k, auth := range creds { - authConfigs[k] = registrytypes.AuthConfig(auth) + for k, authConfig := range creds { + authConfigs[k] = registrytypes.AuthConfig{ + Username: authConfig.Username, + Password: authConfig.Password, + ServerAddress: authConfig.ServerAddress, + + // TODO(thaJeztah): Are these expected to be included? + Auth: authConfig.Auth, + IdentityToken: authConfig.IdentityToken, + RegistryToken: authConfig.RegistryToken, + } } buildOpts := imageBuildOptions(dockerCli, options) buildOpts.Version = buildtypes.BuilderV1 diff --git a/cli/command/registry.go b/cli/command/registry.go index dd6e74d4d46f..0deb74cd9170 100644 --- a/cli/command/registry.go +++ b/cli/command/registry.go @@ -77,7 +77,16 @@ func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInf } a, _ := cfg.GetAuthConfig(configKey) - return registrytypes.AuthConfig(a) + return registrytypes.AuthConfig{ + Username: a.Username, + Password: a.Password, + ServerAddress: a.ServerAddress, + + // TODO(thaJeztah): Are these expected to be included? + Auth: a.Auth, + IdentityToken: a.IdentityToken, + RegistryToken: a.RegistryToken, + } } // GetDefaultAuthConfig gets the default auth config given a serverAddress @@ -86,19 +95,27 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve if !isDefaultRegistry { serverAddress = credentials.ConvertToHostname(serverAddress) } - authconfig := configtypes.AuthConfig{} + authCfg := configtypes.AuthConfig{} var err error if checkCredStore { - authconfig, err = cfg.GetAuthConfig(serverAddress) + authCfg, err = cfg.GetAuthConfig(serverAddress) if err != nil { return registrytypes.AuthConfig{ ServerAddress: serverAddress, }, err } } - authconfig.ServerAddress = serverAddress - authconfig.IdentityToken = "" - return registrytypes.AuthConfig(authconfig), nil + + return registrytypes.AuthConfig{ + Username: authCfg.Username, + Password: authCfg.Password, + ServerAddress: serverAddress, + + // TODO(thaJeztah): Are these expected to be included? + Auth: authCfg.Auth, + IdentityToken: "", + RegistryToken: authCfg.RegistryToken, + }, nil } // PromptUserForCredentials handles the CLI prompt for the user to input @@ -213,7 +230,16 @@ func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (strin return "", err } - encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig)) + encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{ + Username: authConfig.Username, + Password: authConfig.Password, + ServerAddress: authConfig.ServerAddress, + + // TODO(thaJeztah): Are these expected to be included? + Auth: authConfig.Auth, + IdentityToken: authConfig.IdentityToken, + RegistryToken: authConfig.RegistryToken, + }) if err != nil { return "", err } diff --git a/cli/command/registry/login.go b/cli/command/registry/login.go index 09e997c80090..d8b48210051a 100644 --- a/cli/command/registry/login.go +++ b/cli/command/registry/login.go @@ -259,12 +259,30 @@ func loginWithDeviceCodeFlow(ctx context.Context, dockerCLI command.Cli) (msg st return "", err } - response, err := loginWithRegistry(ctx, dockerCLI.Client(), registrytypes.AuthConfig(*authConfig)) + response, err := loginWithRegistry(ctx, dockerCLI.Client(), registrytypes.AuthConfig{ + Username: authConfig.Username, + Password: authConfig.Password, + ServerAddress: authConfig.ServerAddress, + + // TODO(thaJeztah): Are these expected to be included? + Auth: authConfig.Auth, + IdentityToken: authConfig.IdentityToken, + RegistryToken: authConfig.RegistryToken, + }) if err != nil { return "", err } - if err = storeCredentials(dockerCLI.ConfigFile(), registrytypes.AuthConfig(*authConfig)); err != nil { + if err = storeCredentials(dockerCLI.ConfigFile(), registrytypes.AuthConfig{ + Username: authConfig.Username, + Password: authConfig.Password, + ServerAddress: authConfig.ServerAddress, + + // TODO(thaJeztah): Are these expected to be included? + Auth: authConfig.Auth, + IdentityToken: authConfig.IdentityToken, + RegistryToken: authConfig.RegistryToken, + }); err != nil { return "", err } @@ -273,7 +291,16 @@ func loginWithDeviceCodeFlow(ctx context.Context, dockerCLI command.Cli) (msg st func storeCredentials(cfg *configfile.ConfigFile, authConfig registrytypes.AuthConfig) error { creds := cfg.GetCredentialsStore(authConfig.ServerAddress) - if err := creds.Store(configtypes.AuthConfig(authConfig)); err != nil { + if err := creds.Store(configtypes.AuthConfig{ + Username: authConfig.Username, + Password: authConfig.Password, + ServerAddress: authConfig.ServerAddress, + + // TODO(thaJeztah): Are these expected to be included? + Auth: authConfig.Auth, + IdentityToken: authConfig.IdentityToken, + RegistryToken: authConfig.RegistryToken, + }); err != nil { return errors.Errorf("Error saving credentials: %v", err) } diff --git a/cli/command/registry/search.go b/cli/command/registry/search.go index 654fe1440f04..f735cdaeedd1 100644 --- a/cli/command/registry/search.go +++ b/cli/command/registry/search.go @@ -102,7 +102,16 @@ func getAuth(dockerCLI command.Cli, reposName string) (encodedAuth string, err e // "no credentials found"). We'll get an error when search failed, // so fine to ignore in most situations. authConfig, _ := dockerCLI.ConfigFile().GetAuthConfig(authCfgKey) - return registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig)) + return registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{ + Username: authConfig.Username, + Password: authConfig.Password, + ServerAddress: authConfig.ServerAddress, + + // TODO(thaJeztah): Are these expected to be included? + Auth: authConfig.Auth, + IdentityToken: authConfig.IdentityToken, + RegistryToken: authConfig.RegistryToken, + }) } // splitReposSearchTerm breaks a search term into an index name and remote name diff --git a/cli/command/registry_test.go b/cli/command/registry_test.go index 353bfb2fd8fa..853dcc875eae 100644 --- a/cli/command/registry_test.go +++ b/cli/command/registry_test.go @@ -58,8 +58,17 @@ func TestGetDefaultAuthConfig(t *testing.T) { }, } cfg := configfile.New("filename") - for _, authconfig := range testAuthConfigs { - assert.Check(t, cfg.GetCredentialsStore(authconfig.ServerAddress).Store(configtypes.AuthConfig(authconfig))) + for _, authConfig := range testAuthConfigs { + assert.Check(t, cfg.GetCredentialsStore(authConfig.ServerAddress).Store(configtypes.AuthConfig{ + Username: authConfig.Username, + Password: authConfig.Password, + ServerAddress: authConfig.ServerAddress, + + // TODO(thaJeztah): Are these expected to be included? + Auth: authConfig.Auth, + IdentityToken: authConfig.IdentityToken, + RegistryToken: authConfig.RegistryToken, + })) } for _, tc := range testCases { serverAddress := tc.inputServerAddress From d4b7734f18636c1c1e8d8c13e306072dbfa6df4f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 1 Oct 2025 11:46:19 +0200 Subject: [PATCH 173/193] cli/command: deprecate ResolveDefaultContext The ResolveDefaultContext function was exported in [cli@f820766] to allow (unit) testing, but did not document that it was only exported for this purpose. The only external use of this function is in buildx, which uses it in a unit test that can be implemented without this function. This patch deprecates the function so that we can remove it. [cli@f820766]: https://github.com/docker/cli/commit/f820766f6ac57188d96c9ca377f2b4627e90da28 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 311a97a210e70359c9d94781b71f2f57c40435f9) Signed-off-by: Sebastiaan van Stijn --- cli/command/cli.go | 4 ++-- cli/command/defaultcontextstore.go | 7 +++++++ cli/command/defaultcontextstore_test.go | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cli/command/cli.go b/cli/command/cli.go index 4ba904be29bb..e0f8cc28c313 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -272,7 +272,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) cli.contextStore = &ContextStoreWithDefault{ Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig), Resolver: func() (*DefaultContext, error) { - return ResolveDefaultContext(cli.options, *cli.contextStoreConfig) + return resolveDefaultContext(cli.options, *cli.contextStoreConfig) }, } @@ -309,7 +309,7 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile. contextStore := &ContextStoreWithDefault{ Store: store.New(config.ContextStoreDir(), storeConfig), Resolver: func() (*DefaultContext, error) { - return ResolveDefaultContext(opts, storeConfig) + return resolveDefaultContext(opts, storeConfig) }, } endpoint, err := resolveDockerEndpoint(contextStore, resolveContextName(opts, configFile)) diff --git a/cli/command/defaultcontextstore.go b/cli/command/defaultcontextstore.go index 9b49b3af2a68..6a01a3c68fc8 100644 --- a/cli/command/defaultcontextstore.go +++ b/cli/command/defaultcontextstore.go @@ -52,7 +52,14 @@ type EndpointDefaultResolver interface { } // ResolveDefaultContext creates a Metadata for the current CLI invocation parameters +// +// Deprecated: this function is exported for testing and meant for internal use. It will be removed in the next release. func ResolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*DefaultContext, error) { + return resolveDefaultContext(opts, config) +} + +// resolveDefaultContext creates a Metadata for the current CLI invocation parameters +func resolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*DefaultContext, error) { contextTLSData := store.ContextTLSData{ Endpoints: make(map[string]store.EndpointTLSData), } diff --git a/cli/command/defaultcontextstore_test.go b/cli/command/defaultcontextstore_test.go index 4facfc985337..927298cb6669 100644 --- a/cli/command/defaultcontextstore_test.go +++ b/cli/command/defaultcontextstore_test.go @@ -59,7 +59,7 @@ func TestDefaultContextInitializer(t *testing.T) { assert.NilError(t, err) t.Setenv("DOCKER_HOST", "ssh://someswarmserver") cli.configFile = &configfile.ConfigFile{} - ctx, err := ResolveDefaultContext(&cliflags.ClientOptions{ + ctx, err := resolveDefaultContext(&cliflags.ClientOptions{ TLS: true, TLSOptions: &tlsconfig.Options{ CAFile: "./testdata/ca.pem", From 9d9adf63461e49b11bc7b21d39c3e918db544f74 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 1 Oct 2025 11:59:56 +0200 Subject: [PATCH 174/193] gha: add macOS 15, remove macOS 13 (deprecated) The macOS 13 runners are deprecated and will be removed on December 4th, with brownouts in November; https://github.blog/changelog/2025-09-19-github-actions-macos-13-runner-image-is-closing-down/ Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 91d8c0bf629e342c55ce940ba94885bdef5cca9d) Signed-off-by: Sebastiaan van Stijn --- .github/workflows/test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4c36cebbd26b..97d1a263b536 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,8 +53,9 @@ jobs: fail-fast: false matrix: os: - - macos-13 # macOS 13 on Intel - - macos-14 # macOS 14 on arm64 (Apple Silicon M1) + - macos-14 # macOS 14 on arm64 (Apple Silicon M1) + - macos-15-intel # macOS 15 on Intel + - macos-15 # macOS 15 on arm64 (Apple Silicon M1) # - windows-2022 # FIXME: some tests are failing on the Windows runner, as well as on Appveyor since June 24, 2018: https://ci.appveyor.com/project/docker/cli/history steps: - From f784471104b9af7e150668f8cf7c4b4dbae32661 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 30 Sep 2025 11:45:47 +0200 Subject: [PATCH 175/193] vendor: github.com/docker/docker cd048300a487 (v28.5.0-dev) full diff: https://github.com/moby/moby/compare/v28.5.0-rc.1...cd048300a48700294339c9c91d2dcc691cb8f63b Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 2 +- vendor.sum | 4 +-- .../github.com/docker/docker/api/swagger.yaml | 22 ++++---------- .../docker/api/types/container/hostconfig.go | 7 ++++- .../docker/docker/api/types/system/info.go | 30 +++++++++++-------- vendor/modules.txt | 2 +- 6 files changed, 32 insertions(+), 35 deletions(-) diff --git a/vendor.mod b/vendor.mod index 4b47f351825c..a611a77d3c1c 100644 --- a/vendor.mod +++ b/vendor.mod @@ -16,7 +16,7 @@ require ( github.com/distribution/reference v0.6.0 github.com/docker/cli-docs-tool v0.10.0 github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.5.0-rc.1+incompatible + github.com/docker/docker v28.5.0-rc.1.0.20251001152618-cd048300a487+incompatible // 28.x branch (v28.5.0-dev) github.com/docker/docker-credential-helpers v0.9.3 github.com/docker/go-connections v0.6.0 github.com/docker/go-units v0.5.0 diff --git a/vendor.sum b/vendor.sum index 4bddc6744b03..aa5f7783369c 100644 --- a/vendor.sum +++ b/vendor.sum @@ -57,8 +57,8 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.5.0-rc.1+incompatible h1:cKKJLCI3dTn6olzoIUVJtQ2QVOVtPMUkl0M9U/LMZI4= -github.com/docker/docker v28.5.0-rc.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.5.0-rc.1.0.20251001152618-cd048300a487+incompatible h1:XSOGPdFlTV1ciuzfKp+9/qcx3Ukka+95OoMnty1wFaI= +github.com/docker/docker v28.5.0-rc.1.0.20251001152618-cd048300a487+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= diff --git a/vendor/github.com/docker/docker/api/swagger.yaml b/vendor/github.com/docker/docker/api/swagger.yaml index e8e3f610c7c8..3b10b10c78d8 100644 --- a/vendor/github.com/docker/docker/api/swagger.yaml +++ b/vendor/github.com/docker/docker/api/swagger.yaml @@ -81,7 +81,6 @@ info: { "username": "string", "password": "string", - "email": "string", "serveraddress": "string" } ``` @@ -637,6 +636,9 @@ definitions: by the default (runc) runtime. This field is omitted when empty. + + **Deprecated**: This field is deprecated as kernel 6.12 has deprecated `memory.kmem.tcp.limit_in_bytes` field + for cgroups v1. This field will be removed in a future release. type: "integer" format: "int64" MemoryReservation: @@ -2005,14 +2007,6 @@ definitions: format: "int64" x-nullable: false example: 1239828 - VirtualSize: - description: | - Total size of the image including all layers it is composed of. - - Deprecated: this field is omitted in API v1.44, but kept for backward compatibility. Use Size instead. - type: "integer" - format: "int64" - example: 1239828 GraphDriver: $ref: "#/definitions/DriverData" RootFS: @@ -2145,14 +2139,6 @@ definitions: format: "int64" x-nullable: false example: 1239828 - VirtualSize: - description: |- - Total size of the image including all layers it is composed of. - - Deprecated: this field is omitted in API v1.44, but kept for backward compatibility. Use Size instead. - type: "integer" - format: "int64" - example: 172064416 Labels: description: "User-defined key/value metadata." type: "object" @@ -6351,6 +6337,8 @@ definitions: Kernel memory TCP limits are not supported when using cgroups v2, which does not support the corresponding `memory.kmem.tcp.limit_in_bytes` cgroup. + + **Deprecated**: This field is deprecated as kernel 6.12 has deprecated kernel memory TCP accounting. type: "boolean" example: true CpuCfsPeriod: diff --git a/vendor/github.com/docker/docker/api/types/container/hostconfig.go b/vendor/github.com/docker/docker/api/types/container/hostconfig.go index f63f049c7c25..7a41436cc702 100644 --- a/vendor/github.com/docker/docker/api/types/container/hostconfig.go +++ b/vendor/github.com/docker/docker/api/types/container/hostconfig.go @@ -394,7 +394,12 @@ type Resources struct { // KernelMemory specifies the kernel memory limit (in bytes) for the container. // Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes. - KernelMemory int64 `json:",omitempty"` + KernelMemory int64 `json:",omitempty"` + // Hard limit for kernel TCP buffer memory (in bytes). + // + // Deprecated: This field is deprecated and will be removed in the next release. + // Starting with 6.12, the kernel has deprecated kernel memory tcp accounting + // for cgroups v1. KernelMemoryTCP int64 `json:",omitempty"` // Hard limit for kernel TCP buffer memory (in bytes) MemoryReservation int64 // Memory soft limit (in bytes) MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap diff --git a/vendor/github.com/docker/docker/api/types/system/info.go b/vendor/github.com/docker/docker/api/types/system/info.go index 047639ed91e2..0f39099d8ab5 100644 --- a/vendor/github.com/docker/docker/api/types/system/info.go +++ b/vendor/github.com/docker/docker/api/types/system/info.go @@ -9,19 +9,23 @@ import ( // Info contains response of Engine API: // GET "/info" type Info struct { - ID string - Containers int - ContainersRunning int - ContainersPaused int - ContainersStopped int - Images int - Driver string - DriverStatus [][2]string - SystemStatus [][2]string `json:",omitempty"` // SystemStatus is only propagated by the Swarm standalone API - Plugins PluginsInfo - MemoryLimit bool - SwapLimit bool - KernelMemory bool `json:",omitempty"` // Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes + ID string + Containers int + ContainersRunning int + ContainersPaused int + ContainersStopped int + Images int + Driver string + DriverStatus [][2]string + SystemStatus [][2]string `json:",omitempty"` // SystemStatus is only propagated by the Swarm standalone API + Plugins PluginsInfo + MemoryLimit bool + SwapLimit bool + KernelMemory bool `json:",omitempty"` // Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes + // KernelMemoryLimit is not supported on cgroups v2. + // + // Deprecated: This field is deprecated and will be removed in the next release. + // Starting with kernel 6.12, the kernel has deprecated kernel memory tcp accounting KernelMemoryTCP bool `json:",omitempty"` // KernelMemoryTCP is not supported on cgroups v2. CPUCfsPeriod bool `json:"CpuCfsPeriod"` CPUCfsQuota bool `json:"CpuCfsQuota"` diff --git a/vendor/modules.txt b/vendor/modules.txt index 8bfe39166d26..7f10dadc6102 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/uuid -# github.com/docker/docker v28.5.0-rc.1+incompatible +# github.com/docker/docker v28.5.0-rc.1.0.20251001152618-cd048300a487+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types From 0d799c556f08545346be009feeede4d6da7973bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Tue, 7 Oct 2025 21:46:51 +0200 Subject: [PATCH 176/193] update to go1.24.8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This minor release includes 10 security fixes following the security policy: - net/mail: excessive CPU consumption in ParseAddress The ParseAddress function constructed domain-literal address components through repeated string concatenation. When parsing large domain-literal components, this could cause excessive CPU consumption. Thanks to Philippe Antoine (Catena cyber) for reporting this issue. This is CVE-2025-61725 and Go issue https://go.dev/issue/75680. - crypto/x509: quadratic complexity when checking name constraints Due to the design of the name constraint checking algorithm, the processing time of some inputs scales non-linearly with respect to the size of the certificate. This affects programs which validate arbitrary certificate chains. Thanks to Jakub Ciolek for reporting this issue. This is CVE-2025-58187 and Go issue https://go.dev/issue/75681. - crypto/tls: ALPN negotiation errors can contain arbitrary text The crypto/tls conn.Handshake method returns an error on the server-side when ALPN negotation fails which can contain arbitrary attacker controlled information provided by the client-side of the connection which is not escaped. This affects programs which log these errors without any additional form of sanitization, and may allow injection of attacker controlled information into logs. Thanks to National Cyber Security Centre Finland for reporting this issue. This is CVE-2025-58189 and Go issue https://go.dev/issue/75652. - encoding/pem: quadratic complexity when parsing some invalid inputs Due to the design of the PEM parsing function, the processing time for some inputs scales non-linearly with respect to the size of the input. This affects programs which parse untrusted PEM inputs. Thanks to Jakub Ciolek for reporting this issue. This is CVE-2025-61723 and Go issue https://go.dev/issue/75676. - net/url: insufficient validation of bracketed IPv6 hostnames The Parse function permitted values other than IPv6 addresses to be included in square brackets within the host component of a URL. RFC 3986 permits IPv6 addresses to be included within the host component, enclosed within square brackets. For example: "http://[::1]/". IPv4 addresses and hostnames must not appear within square brackets. Parse did not enforce this requirement. Thanks to Enze Wang, Jingcheng Yang and Zehui Miao of Tsinghua University for reporting this issue. This is CVE-2025-47912 and Go issue https://go.dev/issue/75678. - encoding/asn1: pre-allocating memory when parsing DER payload can cause memory exhaustion When parsing DER payloads, memories were being allocated prior to fully validating the payloads. This permits an attacker to craft a big empty DER payload to cause memory exhaustion in functions such as asn1.Unmarshal, x509.ParseCertificateRequest, and ocsp.ParseResponse. Thanks to Jakub Ciolek for reporting this issue. This is CVE-2025-58185 and Go issue https://go.dev/issue/75671. - net/http: lack of limit when parsing cookies can cause memory exhaustion Despite HTTP headers having a default limit of 1 MB, the number of cookies that can be parsed did not have a limit. By sending a lot of very small cookies such as "a=;", an attacker can make an HTTP server allocate a large amount of structs, causing large memory consumption. net/http now limits the number of cookies accepted to 3000, which can be adjusted using the httpcookiemaxnum GODEBUG option. Thanks to jub0bs for reporting this issue. This is CVE-2025-58186 and Go issue https://go.dev/issue/75672. - crypto/x509: panic when validating certificates with DSA public keys Validating certificate chains which contain DSA public keys can cause programs to panic, due to a interface cast that assumes they implement the Equal method. This affects programs which validate arbitrary certificate chains. Thanks to Jakub Ciolek for reporting this issue. This is CVE-2025-58188 and Go issue https://go.dev/issue/75675. - archive/tar: unbounded allocation when parsing GNU sparse map tar.Reader did not set a maximum size on the number of sparse region data blocks in GNU tar pax 1.0 sparse files. A maliciously-crafted archive containing a large number of sparse regions could cause a Reader to read an unbounded amount of data from the archive into memory. When reading from a compressed source, a small compressed input could result in large allocations. Thanks to Harshit Gupta (Mr HAX) - https://www.linkedin.com/in/iam-harshit-gupta/ for reporting this issue. This is CVE-2025-58183 and Go issue https://go.dev/issue/75677. - net/textproto: excessive CPU consumption in Reader.ReadResponse The Reader.ReadResponse function constructed a response string through repeated string concatenation of lines. When the number of lines in a response is large, this could cause excessive CPU consumption. Thanks to Jakub Ciolek for reporting this issue. This is CVE-2025-61724 and Go issue https://go.dev/issue/75716. Signed-off-by: Paweł Gronowski (cherry picked from commit e598ea01762e8798ed9ff68948138c88d0585b14) Signed-off-by: Paweł Gronowski --- .github/workflows/codeql.yml | 2 +- .github/workflows/test.yml | 2 +- .golangci.yml | 2 +- Dockerfile | 2 +- docker-bake.hcl | 2 +- dockerfiles/Dockerfile.dev | 2 +- dockerfiles/Dockerfile.lint | 2 +- dockerfiles/Dockerfile.vendor | 2 +- e2e/testdata/Dockerfile.gencerts | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ce4e9b81f8cf..34a5c1aab420 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -63,7 +63,7 @@ jobs: name: Update Go uses: actions/setup-go@v6 with: - go-version: "1.24.7" + go-version: "1.24.8" - name: Initialize CodeQL uses: github/codeql-action/init@v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 97d1a263b536..41bea03bce7d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,7 +67,7 @@ jobs: name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.24.7" + go-version: "1.24.8" - name: Test run: | diff --git a/.golangci.yml b/.golangci.yml index 01273f2d28a1..280e7efa9e93 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,7 @@ run: # which causes it to fallback to go1.17 semantics. # # TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24 - go: "1.24.7" + go: "1.24.8" timeout: 5m diff --git a/Dockerfile b/Dockerfile index aef1a3c73669..08b72746df05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ARG BASE_VARIANT=alpine ARG ALPINE_VERSION=3.21 ARG BASE_DEBIAN_DISTRO=bookworm -ARG GO_VERSION=1.24.7 +ARG GO_VERSION=1.24.8 ARG XX_VERSION=1.6.1 ARG GOVERSIONINFO_VERSION=v1.4.1 diff --git a/docker-bake.hcl b/docker-bake.hcl index 99c62722c536..d24daadd8dd4 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,5 +1,5 @@ variable "GO_VERSION" { - default = "1.24.7" + default = "1.24.8" } variable "VERSION" { default = "" diff --git a/dockerfiles/Dockerfile.dev b/dockerfiles/Dockerfile.dev index 9446d249277d..502932f8f73d 100644 --- a/dockerfiles/Dockerfile.dev +++ b/dockerfiles/Dockerfile.dev @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.7 +ARG GO_VERSION=1.24.8 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/dockerfiles/Dockerfile.lint b/dockerfiles/Dockerfile.lint index d5babf5e19b7..2eee45c18f62 100644 --- a/dockerfiles/Dockerfile.lint +++ b/dockerfiles/Dockerfile.lint @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.7 +ARG GO_VERSION=1.24.8 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/dockerfiles/Dockerfile.vendor b/dockerfiles/Dockerfile.vendor index 633a9e88721c..a7ab301b154d 100644 --- a/dockerfiles/Dockerfile.vendor +++ b/dockerfiles/Dockerfile.vendor @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.7 +ARG GO_VERSION=1.24.8 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/e2e/testdata/Dockerfile.gencerts b/e2e/testdata/Dockerfile.gencerts index 2063c2bc0a78..1f2ae4160316 100644 --- a/e2e/testdata/Dockerfile.gencerts +++ b/e2e/testdata/Dockerfile.gencerts @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.7 +ARG GO_VERSION=1.24.8 FROM golang:${GO_VERSION}-alpine AS generated ENV GOTOOLCHAIN=local From e8b22ce03c0a4f639bcdad70bbfed76e4bfe37c6 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 8 Oct 2025 16:17:13 +0200 Subject: [PATCH 177/193] vendor: github.com/docker/docker v28.5.1 Signed-off-by: Sebastiaan van Stijn --- vendor.mod | 2 +- vendor.sum | 4 ++-- .../github.com/docker/docker/api/swagger.yaml | 19 +++++++++++++++++-- .../docker/api/types/image/image_inspect.go | 4 ++++ .../docker/docker/api/types/plugin.go | 6 +++++- vendor/modules.txt | 2 +- 6 files changed, 30 insertions(+), 7 deletions(-) diff --git a/vendor.mod b/vendor.mod index a611a77d3c1c..336c2fd26069 100644 --- a/vendor.mod +++ b/vendor.mod @@ -16,7 +16,7 @@ require ( github.com/distribution/reference v0.6.0 github.com/docker/cli-docs-tool v0.10.0 github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.5.0-rc.1.0.20251001152618-cd048300a487+incompatible // 28.x branch (v28.5.0-dev) + github.com/docker/docker v28.5.1+incompatible github.com/docker/docker-credential-helpers v0.9.3 github.com/docker/go-connections v0.6.0 github.com/docker/go-units v0.5.0 diff --git a/vendor.sum b/vendor.sum index aa5f7783369c..0ccf6c1a6d39 100644 --- a/vendor.sum +++ b/vendor.sum @@ -57,8 +57,8 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.5.0-rc.1.0.20251001152618-cd048300a487+incompatible h1:XSOGPdFlTV1ciuzfKp+9/qcx3Ukka+95OoMnty1wFaI= -github.com/docker/docker v28.5.0-rc.1.0.20251001152618-cd048300a487+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= +github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= diff --git a/vendor/github.com/docker/docker/api/swagger.yaml b/vendor/github.com/docker/docker/api/swagger.yaml index 3b10b10c78d8..6ca2c2b0863f 100644 --- a/vendor/github.com/docker/docker/api/swagger.yaml +++ b/vendor/github.com/docker/docker/api/swagger.yaml @@ -1938,6 +1938,11 @@ definitions: Depending on how the image was created, this field may be empty and is only set for images that were built/created locally. This field is empty if the image was pulled from an image registry. + + > **Deprecated**: This field is only set when using the deprecated + > legacy builder. It is included in API responses for informational + > purposes, but should not be depended on as it will be omitted + > once the legacy builder is removed. type: "string" x-nullable: false example: "" @@ -1963,6 +1968,11 @@ definitions: The version of Docker that was used to build the image. Depending on how the image was created, this field may be empty. + + > **Deprecated**: This field is only set when using the deprecated + > legacy builder. It is included in API responses for informational + > purposes, but should not be depended on as it will be omitted + > once the legacy builder is removed. type: "string" x-nullable: false example: "27.0.1" @@ -3132,10 +3142,15 @@ definitions: - Args properties: DockerVersion: - description: "Docker Version used to create the plugin" + description: |- + Docker Version used to create the plugin. + + Depending on how the plugin was created, this field may be empty or omitted. + + Deprecated: this field is no longer set, and will be removed in the next API version. type: "string" x-nullable: false - example: "17.06.0-ce" + x-omitempty: true Description: type: "string" x-nullable: false diff --git a/vendor/github.com/docker/docker/api/types/image/image_inspect.go b/vendor/github.com/docker/docker/api/types/image/image_inspect.go index 3bdb474287c0..1bec0b72b287 100644 --- a/vendor/github.com/docker/docker/api/types/image/image_inspect.go +++ b/vendor/github.com/docker/docker/api/types/image/image_inspect.go @@ -48,6 +48,8 @@ type InspectResponse struct { // Depending on how the image was created, this field may be empty and // is only set for images that were built/created locally. This field // is empty if the image was pulled from an image registry. + // + // Deprecated: this field is deprecated, and will be removed in the next release. Parent string // Comment is an optional message that can be set when committing or @@ -80,6 +82,8 @@ type InspectResponse struct { // DockerVersion is the version of Docker that was used to build the image. // // Depending on how the image was created, this field may be empty. + // + // Deprecated: this field is deprecated, and will be removed in the next release. DockerVersion string // Author is the name of the author that was specified when committing the diff --git a/vendor/github.com/docker/docker/api/types/plugin.go b/vendor/github.com/docker/docker/api/types/plugin.go index abae48b9ab01..a9eff28a0485 100644 --- a/vendor/github.com/docker/docker/api/types/plugin.go +++ b/vendor/github.com/docker/docker/api/types/plugin.go @@ -42,7 +42,11 @@ type PluginConfig struct { // Required: true Description string `json:"Description"` - // Docker Version used to create the plugin + // Docker Version used to create the plugin. + // + // Depending on how the plugin was created, this field may be empty or omitted. + // + // Deprecated: this field is no longer set, and will be removed in the next API version. DockerVersion string `json:"DockerVersion,omitempty"` // documentation diff --git a/vendor/modules.txt b/vendor/modules.txt index 7f10dadc6102..422b0fe05b4e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/uuid -# github.com/docker/docker v28.5.0-rc.1.0.20251001152618-cd048300a487+incompatible +# github.com/docker/docker v28.5.1+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types From 4737ed4906dd4b511418c741adf5f592e2f77ed4 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 10 Oct 2025 17:21:57 +0200 Subject: [PATCH 178/193] cli/command/image/build: fix linting, add sub-tests - fix minor linting issues (unhandled errors) - rename vars to prevent shadowing - use sub-tests for tests that already prepared for it Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 2c539a6530ec318f5aedb2f3dbb1db870a8d410b) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build/context.go | 30 ++++++++++++---------- cli/command/image/build/context_test.go | 34 +++++++++++++++---------- cli/command/image/build/dockerignore.go | 4 ++- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/cli/command/image/build/context.go b/cli/command/image/build/context.go index add175c2c0f3..a61185108c75 100644 --- a/cli/command/image/build/context.go +++ b/cli/command/image/build/context.go @@ -80,7 +80,7 @@ func ValidateContextDirectory(srcPath string, excludes []string) error { if err != nil && os.IsPermission(err) { return errors.Errorf("no permission to read from '%s'", filePath) } - currentFile.Close() + _ = currentFile.Close() } return nil }) @@ -97,10 +97,10 @@ func filepathMatches(matcher *patternmatcher.PatternMatcher, file string) (bool, // DetectArchiveReader detects whether the input stream is an archive or a // Dockerfile and returns a buffered version of input, safe to consume in lieu -// of input. If an archive is detected, isArchive is set to true, and to false +// of input. If an archive is detected, ok is set to true, and to false // otherwise, in which case it is safe to assume input represents the contents // of a Dockerfile. -func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, isArchive bool, err error) { +func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err error) { buf := bufio.NewReader(input) magic, err := buf.Peek(archiveHeaderSize * 2) @@ -141,12 +141,12 @@ func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) { // Dockerfile or tar archive. Returns a tar archive used as a context and a // path to the Dockerfile inside the tar. func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) { - rc, isArchive, err := DetectArchiveReader(rc) + rc, ok, err := DetectArchiveReader(rc) if err != nil { return nil, "", err } - if isArchive { + if ok { return rc, dockerfileName, nil } @@ -171,7 +171,7 @@ func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadC return newReadCloserWrapper(tarArchive, func() error { err := tarArchive.Close() - os.RemoveAll(dockerfileDir) + _ = os.RemoveAll(dockerfileDir) return err }), DefaultDockerfileName, nil } @@ -242,7 +242,7 @@ func getWithStatusError(url string) (resp *http.Response, err error) { } msg := fmt.Sprintf("failed to GET %s with status %s", url, resp.Status) body, err := io.ReadAll(resp.Body) - resp.Body.Close() + _ = resp.Body.Close() if err != nil { return nil, errors.Wrapf(err, "%s: error reading body", msg) } @@ -374,7 +374,7 @@ func isUNC(path string) bool { // the relative path to the dockerfile in the context. func AddDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCloser) (io.ReadCloser, string, error) { file, err := io.ReadAll(dockerfileCtx) - dockerfileCtx.Close() + _ = dockerfileCtx.Close() if err != nil { return nil, "", err } @@ -438,17 +438,19 @@ func Compress(buildCtx io.ReadCloser) (io.ReadCloser, error) { go func() { compressWriter, err := compression.CompressStream(pipeWriter, archive.Gzip) if err != nil { - pipeWriter.CloseWithError(err) + _ = pipeWriter.CloseWithError(err) } - defer buildCtx.Close() + defer func() { + _ = buildCtx.Close() + }() if _, err := io.Copy(compressWriter, buildCtx); err != nil { - pipeWriter.CloseWithError(errors.Wrap(err, "failed to compress context")) - compressWriter.Close() + _ = pipeWriter.CloseWithError(errors.Wrap(err, "failed to compress context")) + _ = compressWriter.Close() return } - compressWriter.Close() - pipeWriter.Close() + _ = compressWriter.Close() + _ = pipeWriter.Close() }() return pipeReader, nil diff --git a/cli/command/image/build/context_test.go b/cli/command/image/build/context_test.go index 45533d6ebb4c..f7ebce2413f9 100644 --- a/cli/command/image/build/context_test.go +++ b/cli/command/image/build/context_test.go @@ -135,7 +135,7 @@ func TestGetContextFromReaderString(t *testing.T) { } buff := new(bytes.Buffer) - buff.ReadFrom(tarReader) + _, _ = buff.ReadFrom(tarReader) contents := buff.String() _, err = tarReader.Next() @@ -182,7 +182,7 @@ func TestGetContextFromReaderTar(t *testing.T) { } buff := new(bytes.Buffer) - buff.ReadFrom(tarReader) + _, _ = buff.ReadFrom(tarReader) contents := buff.String() _, err = tarReader.Next() @@ -263,7 +263,7 @@ func chdir(t *testing.T, dir string) { } func TestIsArchive(t *testing.T) { - testcases := []struct { + tests := []struct { doc string header []byte expected bool @@ -289,13 +289,15 @@ func TestIsArchive(t *testing.T) { expected: false, }, } - for _, testcase := range testcases { - assert.Check(t, is.Equal(testcase.expected, IsArchive(testcase.header)), testcase.doc) + for _, tc := range tests { + t.Run(tc.doc, func(t *testing.T) { + assert.Check(t, is.Equal(tc.expected, IsArchive(tc.header)), tc.doc) + }) } } func TestDetectArchiveReader(t *testing.T) { - testcases := []struct { + tests := []struct { file string desc string expected bool @@ -316,14 +318,18 @@ func TestDetectArchiveReader(t *testing.T) { expected: false, }, } - for _, testcase := range testcases { - content, err := os.Open(testcase.file) - assert.NilError(t, err) - defer content.Close() - - _, isArchive, err := DetectArchiveReader(content) - assert.NilError(t, err) - assert.Check(t, is.Equal(testcase.expected, isArchive), testcase.file) + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + content, err := os.Open(tc.file) + assert.NilError(t, err) + defer func() { + _ = content.Close() + }() + + _, isArchive, err := DetectArchiveReader(content) + assert.NilError(t, err) + assert.Check(t, is.Equal(tc.expected, isArchive), tc.file) + }) } } diff --git a/cli/command/image/build/dockerignore.go b/cli/command/image/build/dockerignore.go index 357210437c03..8d11aa5e10d6 100644 --- a/cli/command/image/build/dockerignore.go +++ b/cli/command/image/build/dockerignore.go @@ -21,7 +21,9 @@ func ReadDockerignore(contextDir string) ([]string, error) { case err != nil: return nil, err } - defer f.Close() + defer func() { + _ = f.Close() + }() patterns, err := ignorefile.ReadAll(f) if err != nil { From 22487ad6f6990c373db82163d7797f601ff92b73 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 10 Oct 2025 17:23:57 +0200 Subject: [PATCH 179/193] cli/command/image/build: deprecate IsArchive utility It was only used internally. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 64be664e858d6ebcf0a119e349f6667ef67e7a83) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build/context.go | 10 +++++++++- cli/command/image/build/context_test.go | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cli/command/image/build/context.go b/cli/command/image/build/context.go index a61185108c75..ca57d6ecb85f 100644 --- a/cli/command/image/build/context.go +++ b/cli/command/image/build/context.go @@ -108,7 +108,7 @@ func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err er return nil, false, errors.Errorf("failed to peek context header from STDIN: %v", err) } - return newReadCloserWrapper(buf, func() error { return input.Close() }), IsArchive(magic), nil + return newReadCloserWrapper(buf, func() error { return input.Close() }), isArchive(magic), nil } // WriteTempDockerfile writes a Dockerfile stream to a temporary file with a @@ -178,7 +178,15 @@ func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadC // IsArchive checks for the magic bytes of a tar or any supported compression // algorithm. +// +// Deprecated: this utility was used internally and will be removed in the next release. func IsArchive(header []byte) bool { + return isArchive(header) +} + +// isArchive checks for the magic bytes of a tar or any supported compression +// algorithm. +func isArchive(header []byte) bool { if compression.Detect(header) != compression.None { return true } diff --git a/cli/command/image/build/context_test.go b/cli/command/image/build/context_test.go index f7ebce2413f9..8630d81146fe 100644 --- a/cli/command/image/build/context_test.go +++ b/cli/command/image/build/context_test.go @@ -291,7 +291,7 @@ func TestIsArchive(t *testing.T) { } for _, tc := range tests { t.Run(tc.doc, func(t *testing.T) { - assert.Check(t, is.Equal(tc.expected, IsArchive(tc.header)), tc.doc) + assert.Check(t, is.Equal(tc.expected, isArchive(tc.header)), tc.doc) }) } } From 7063a0ef077e2030b7c0d78c0e817e69d4c2a05d Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 10 Oct 2025 17:25:04 +0200 Subject: [PATCH 180/193] cli/command/image: runBuild: inline vars and minor cleanups Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 9e646f6d922610eea8c3f6f4f4a3add184b3a183) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cli/command/image/build.go b/cli/command/image/build.go index c0b002390cb7..aebb421ee40a 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -184,7 +184,6 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error { //nolint:gocyclo func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) error { var ( - err error buildCtx io.ReadCloser dockerfileCtx io.ReadCloser contextDir string @@ -266,7 +265,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) } if err := build.ValidateContextDirectory(contextDir, excludes); err != nil { - return errors.Wrap(err, "error checking context") + return errors.Wrap(err, "checking context") } // And canonicalize dockerfile name to a platform-independent one @@ -353,7 +352,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) } } buildOpts := imageBuildOptions(dockerCli, options) - buildOpts.Version = buildtypes.BuilderV1 buildOpts.Dockerfile = relDockerfile buildOpts.AuthConfigs = authConfigs buildOpts.RemoteContext = remote @@ -363,7 +361,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) if options.quiet { _, _ = fmt.Fprintf(dockerCli.Err(), "%s", progBuff) } - cancel() return err } defer response.Body.Close() @@ -380,7 +377,8 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) err = jsonstream.Display(ctx, response.Body, streams.NewOut(buildBuff), jsonstream.WithAuxCallback(aux)) if err != nil { - if jerr, ok := err.(*jsonstream.JSONError); ok { + var jerr *jsonstream.JSONError + if errors.As(err, &jerr) { // If no error code is set, default to 1 if jerr.Code == 0 { jerr.Code = 1 @@ -544,6 +542,7 @@ func replaceDockerfileForContentTrust(ctx context.Context, inputTarStream io.Rea func imageBuildOptions(dockerCli command.Cli, options buildOptions) buildtypes.ImageBuildOptions { configFile := dockerCli.ConfigFile() return buildtypes.ImageBuildOptions{ + Version: buildtypes.BuilderV1, Memory: options.memory.Value(), MemorySwap: options.memorySwap.Value(), Tags: options.tags.GetSlice(), From 36d9523e317d958c4022d7a56c8bf85bbcb9c043 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 9 Oct 2025 13:46:33 +0200 Subject: [PATCH 181/193] opts: deprecate ValidateMACAddress It was a wrapper around net.ParseMAC from stdlib, so users should use that directly. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 17d6a929548ae0693affef54f36f03d27f00e603) Signed-off-by: Sebastiaan van Stijn --- cli/command/container/opts.go | 5 +++-- opts/opts.go | 2 ++ opts/opts_test.go | 14 -------------- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index 5ff39c4ee03f..b520d6b13a51 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "net" "os" "path" "path/filepath" @@ -350,7 +351,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con // Validate the input mac address if copts.macAddress != "" { - if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil { + if _, err := net.ParseMAC(strings.TrimSpace(copts.macAddress)); err != nil { return nil, errors.Errorf("%s is not a valid mac address", copts.macAddress) } } @@ -883,7 +884,7 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End } } if ep.MacAddress != "" { - if _, err := opts.ValidateMACAddress(ep.MacAddress); err != nil { + if _, err := net.ParseMAC(strings.TrimSpace(ep.MacAddress)); err != nil { return nil, errors.Errorf("%s is not a valid mac address", ep.MacAddress) } epConfig.MacAddress = ep.MacAddress diff --git a/opts/opts.go b/opts/opts.go index 94eda0560a17..d1072f6ca11c 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -264,6 +264,8 @@ func ValidateIPAddress(val string) (string, error) { } // ValidateMACAddress validates a MAC address. +// +// Deprecated: use [net.ParseMAC]. This function will be removed in the next release. func ValidateMACAddress(val string) (string, error) { _, err := net.ParseMAC(strings.TrimSpace(val)) if err != nil { diff --git a/opts/opts_test.go b/opts/opts_test.go index 7dc87cad9e83..d49c7fb5fca4 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -396,20 +396,6 @@ func TestNamedMapOpts(t *testing.T) { } } -func TestValidateMACAddress(t *testing.T) { - if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil { - t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err) - } - - if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil { - t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC") - } - - if _, err := ValidateMACAddress(`random invalid string`); err == nil { - t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC") - } -} - func TestValidateLink(t *testing.T) { valid := []string{ "name", From d8bab717470622740d6b3007f35294d000cc6860 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 13 Oct 2025 11:47:39 +0200 Subject: [PATCH 182/193] opts: deprecate ListOpts.Delete() This method was added as part of a refactor in [moby@1ba1138], at which time it was used to delete original values for "--host" and "--volume" after normalizing. This beccame redundant in [moby@6200002], which added specialized options that used a validate function, which both validated and normalized inputs. It's no longer used, so let's mark it deprecated so that we can remove it. [moby@1ba1138]: https://github.com/moby/moby/commit/1ba11384bf82f824b0efbab31aaca439cfba1b4f [moby@6200002]: https://github.com/moby/moby/commit/6200002669874f3314856527fecd0c004060913c Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 193db8ec4171e17f2ef443ccc3ad77965f9629d8) Signed-off-by: Sebastiaan van Stijn --- opts/opts.go | 2 ++ opts/opts_test.go | 21 ++++++++------------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index d1072f6ca11c..c555c2d18884 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -60,6 +60,8 @@ func (opts *ListOpts) Set(value string) error { } // Delete removes the specified element from the slice. +// +// Deprecated: this method is no longer used and will be removed in the next release. func (opts *ListOpts) Delete(key string) { for i, k := range *opts.values { if k == key { diff --git a/opts/opts_test.go b/opts/opts_test.go index d49c7fb5fca4..f363a2a6440a 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -142,18 +142,14 @@ func TestListOptsWithoutValidator(t *testing.T) { if o.Get("baz") { t.Error(`o.Get("baz") == true`) } - o.Delete("foo") - if o.String() != "[bar bar]" { - t.Errorf("%s != [bar bar]", o.String()) + if listOpts := o.GetSlice(); len(listOpts) != 3 || listOpts[0] != "foo" || listOpts[1] != "bar" || listOpts[2] != "bar" { + t.Errorf("Expected [[foo bar bar]], got [%v]", listOpts) } - if listOpts := o.GetAll(); len(listOpts) != 2 || listOpts[0] != "bar" || listOpts[1] != "bar" { - t.Errorf("Expected [[bar bar]], got [%v]", listOpts) + if listOpts := o.GetAll(); len(listOpts) != 3 || listOpts[0] != "foo" || listOpts[1] != "bar" || listOpts[2] != "bar" { + t.Errorf("Expected [[foo bar bar]], got [%v]", listOpts) } - if listOpts := o.GetSlice(); len(listOpts) != 2 || listOpts[0] != "bar" || listOpts[1] != "bar" { - t.Errorf("Expected [[bar bar]], got [%v]", listOpts) - } - if mapListOpts := o.GetMap(); len(mapListOpts) != 1 { - t.Errorf("Expected [map[bar:{}]], got [%v]", mapListOpts) + if mapListOpts := o.GetMap(); len(mapListOpts) != 2 { + t.Errorf("Expected [map[bar:{} foo:{}]], got [%v]", mapListOpts) } } @@ -186,9 +182,8 @@ func TestListOptsWithValidator(t *testing.T) { if o.Get("baz") { t.Error(`o.Get("baz") == true`) } - o.Delete("valid-option2=2") - if o.String() != "" { - t.Errorf(`%s != ""`, o.String()) + if expected := "[valid-option2=2]"; o.String() != expected { + t.Errorf(`%s != %q`, o.String(), expected) } } From 4942b2747fd48d7d2c6e9b9473aac02245c2ea83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 08:04:23 +0000 Subject: [PATCH 183/193] build(deps): bump github/codeql-action from 3 to 4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] (cherry picked from commit 5483b10e945bb7d1023d414c59ce8d8231793fd3) Signed-off-by: Sebastiaan van Stijn --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 34a5c1aab420..2766da8af922 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -66,14 +66,14 @@ jobs: go-version: "1.24.8" - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: go - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "/language:go" From 70123c523c5787f20dd50e03697ba32bef4c22df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 08:30:59 +0000 Subject: [PATCH 184/193] build(deps): bump actions/upload-artifact from 4 to 5 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] (cherry picked from commit aef2ef8c7711817e8597cc1f7d461b8e9ab92c5d) Signed-off-by: Sebastiaan van Stijn --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fa2fc9648bb9..b6b14d113f7d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -88,7 +88,7 @@ jobs: fi - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ env.ARTIFACT_NAME }} path: /tmp/out/* From 4c7d52534f05c0a9733e0f9d926afdeef445734c Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 10 Oct 2025 23:55:52 +0200 Subject: [PATCH 185/193] Dockerfile: bump gotest.tools/gotestsum v1.13.0 full diff: https://github.com/gotestyourself/gotestsum/compare/v1.12.3...v1.13.0 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit f8b1b8d165688b4048fbff7f98d3687ef18af8e8) Signed-off-by: Sebastiaan van Stijn --- Dockerfile | 2 +- dockerfiles/Dockerfile.dev | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 08b72746df05..5bbda00c0ab5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ ARG GOVERSIONINFO_VERSION=v1.4.1 # GOTESTSUM_VERSION sets the version of gotestsum to install in the dev container. # It must be a valid tag in the https://github.com/gotestyourself/gotestsum repository. -ARG GOTESTSUM_VERSION=v1.12.3 +ARG GOTESTSUM_VERSION=v1.13.0 # BUILDX_VERSION sets the version of buildx to use for the e2e tests. # It must be a tag in the docker.io/docker/buildx-bin image repository diff --git a/dockerfiles/Dockerfile.dev b/dockerfiles/Dockerfile.dev index 502932f8f73d..c5c2a35f816b 100644 --- a/dockerfiles/Dockerfile.dev +++ b/dockerfiles/Dockerfile.dev @@ -28,7 +28,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ FROM golang AS gotestsum # GOTESTSUM_VERSION sets the version of gotestsum to install in the dev container. # It must be a valid tag in the https://github.com/gotestyourself/gotestsum repository. -ARG GOTESTSUM_VERSION=v1.12.3 +ARG GOTESTSUM_VERSION=v1.13.0 RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg/mod \ --mount=type=tmpfs,target=/go/src/ \ From fbf5c8a86d7629319281a2bec227aad4a9b2fd59 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 13 Oct 2025 10:04:32 +0200 Subject: [PATCH 186/193] Dockerfile: update golangci-lint to v2.5.0 Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 5ad9fbdef7551f0b829f7ed17f55145cce31a03b) Signed-off-by: Sebastiaan van Stijn --- cli/command/formatter/tabwriter/tabwriter.go | 2 +- dockerfiles/Dockerfile.lint | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cli/command/formatter/tabwriter/tabwriter.go b/cli/command/formatter/tabwriter/tabwriter.go index e7473cd9bbdb..a03d94ab609c 100644 --- a/cli/command/formatter/tabwriter/tabwriter.go +++ b/cli/command/formatter/tabwriter/tabwriter.go @@ -12,7 +12,7 @@ // based on https://github.com/golang/go/blob/master/src/text/tabwriter/tabwriter.go Last modified 690ac40 on 31 Jan -//nolint:gocyclo,nakedret,unused // ignore linting errors, so that we can stick close to upstream +//nolint:gocyclo,gofumpt,nakedret,unused // ignore linting errors, so that we can stick close to upstream package tabwriter import ( diff --git a/dockerfiles/Dockerfile.lint b/dockerfiles/Dockerfile.lint index 2eee45c18f62..ddd903cc2790 100644 --- a/dockerfiles/Dockerfile.lint +++ b/dockerfiles/Dockerfile.lint @@ -6,7 +6,8 @@ ARG GO_VERSION=1.24.8 # It must be a supported tag in the docker.io/library/alpine image repository # that's also available as alpine image variant for the Golang version used. ARG ALPINE_VERSION=3.21 -ARG GOLANGCI_LINT_VERSION=v2.1.5 +# GOLANGCI_LINT_VERSION sets the version of the golangci/golangci-lint image to use. +ARG GOLANGCI_LINT_VERSION=v2.5.0 FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint From ba53322412e5c2025b111e83cc46945a81e482c4 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 4 Nov 2025 11:10:08 +0100 Subject: [PATCH 187/193] update to go1.24.9 go1.24.9 (released 2025-10-13) includes fixes to the crypto/x509 package. See the Go 1.24.9 milestone on our issue tracker for details: - https://github.com/golang/go/issues?q=milestone%3AGo1.24.9+label%3ACherryPickApproved - full diff: https://github.com/golang/go/compare/go1.24.8...go1.24.9 Signed-off-by: Sebastiaan van Stijn --- .github/workflows/codeql.yml | 2 +- .github/workflows/test.yml | 2 +- .golangci.yml | 2 +- Dockerfile | 2 +- docker-bake.hcl | 2 +- dockerfiles/Dockerfile.dev | 2 +- dockerfiles/Dockerfile.lint | 2 +- dockerfiles/Dockerfile.vendor | 2 +- e2e/testdata/Dockerfile.gencerts | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2766da8af922..0cc0746a4a99 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -63,7 +63,7 @@ jobs: name: Update Go uses: actions/setup-go@v6 with: - go-version: "1.24.8" + go-version: "1.24.9" - name: Initialize CodeQL uses: github/codeql-action/init@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 41bea03bce7d..cbbeeaec7e0e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,7 +67,7 @@ jobs: name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.24.8" + go-version: "1.24.9" - name: Test run: | diff --git a/.golangci.yml b/.golangci.yml index 280e7efa9e93..8940b9accb7c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,7 @@ run: # which causes it to fallback to go1.17 semantics. # # TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24 - go: "1.24.8" + go: "1.24.9" timeout: 5m diff --git a/Dockerfile b/Dockerfile index 5bbda00c0ab5..9acd85f1a478 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ARG BASE_VARIANT=alpine ARG ALPINE_VERSION=3.21 ARG BASE_DEBIAN_DISTRO=bookworm -ARG GO_VERSION=1.24.8 +ARG GO_VERSION=1.24.9 ARG XX_VERSION=1.6.1 ARG GOVERSIONINFO_VERSION=v1.4.1 diff --git a/docker-bake.hcl b/docker-bake.hcl index d24daadd8dd4..109dc213ff92 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,5 +1,5 @@ variable "GO_VERSION" { - default = "1.24.8" + default = "1.24.9" } variable "VERSION" { default = "" diff --git a/dockerfiles/Dockerfile.dev b/dockerfiles/Dockerfile.dev index c5c2a35f816b..d1c81a27cdf3 100644 --- a/dockerfiles/Dockerfile.dev +++ b/dockerfiles/Dockerfile.dev @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.8 +ARG GO_VERSION=1.24.9 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/dockerfiles/Dockerfile.lint b/dockerfiles/Dockerfile.lint index ddd903cc2790..9a89f3311a66 100644 --- a/dockerfiles/Dockerfile.lint +++ b/dockerfiles/Dockerfile.lint @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.8 +ARG GO_VERSION=1.24.9 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/dockerfiles/Dockerfile.vendor b/dockerfiles/Dockerfile.vendor index a7ab301b154d..d9949d70bfeb 100644 --- a/dockerfiles/Dockerfile.vendor +++ b/dockerfiles/Dockerfile.vendor @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.8 +ARG GO_VERSION=1.24.9 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/e2e/testdata/Dockerfile.gencerts b/e2e/testdata/Dockerfile.gencerts index 1f2ae4160316..e084a1faff8d 100644 --- a/e2e/testdata/Dockerfile.gencerts +++ b/e2e/testdata/Dockerfile.gencerts @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.24.8 +ARG GO_VERSION=1.24.9 FROM golang:${GO_VERSION}-alpine AS generated ENV GOTOOLCHAIN=local From 579b72aa06f52e39fd960788be25f067590d4010 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 13 Oct 2025 15:20:32 +0200 Subject: [PATCH 188/193] cli/command/image/build: deprecate DefaultDockerfileName const It was only used internal in the package. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit f24bb4bc76ae9c02e9df9d5bfccff5ee7e3a9ef9) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build/context.go | 19 ++++++++----- cli/command/image/build/context_test.go | 36 ++++++++++++------------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/cli/command/image/build/context.go b/cli/command/image/build/context.go index ca57d6ecb85f..54085c12f822 100644 --- a/cli/command/image/build/context.go +++ b/cli/command/image/build/context.go @@ -25,9 +25,14 @@ import ( "github.com/pkg/errors" ) +// DefaultDockerfileName is the Default filename with Docker commands, read by docker build +// +// Deprecated: this const is no longer used and will be removed in the next release. +const DefaultDockerfileName string = "Dockerfile" + const ( - // DefaultDockerfileName is the Default filename with Docker commands, read by docker build - DefaultDockerfileName string = "Dockerfile" + // defaultDockerfileName is the Default filename with Docker commands, read by docker build + defaultDockerfileName string = "Dockerfile" // archiveHeaderSize is the number of bytes in an archive header archiveHeaderSize = 512 ) @@ -112,7 +117,7 @@ func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err er } // WriteTempDockerfile writes a Dockerfile stream to a temporary file with a -// name specified by DefaultDockerfileName and returns the path to the +// name specified by defaultDockerfileName and returns the path to the // temporary directory containing the Dockerfile. func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) { // err is a named return value, due to the defer call below. @@ -126,7 +131,7 @@ func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) { } }() - f, err := os.Create(filepath.Join(dockerfileDir, DefaultDockerfileName)) + f, err := os.Create(filepath.Join(dockerfileDir, defaultDockerfileName)) if err != nil { return "", err } @@ -173,7 +178,7 @@ func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadC err := tarArchive.Close() _ = os.RemoveAll(dockerfileDir) return err - }), DefaultDockerfileName, nil + }), defaultDockerfileName, nil } // IsArchive checks for the magic bytes of a tar or any supported compression @@ -326,12 +331,12 @@ func getDockerfileRelPath(absContextDir, givenDockerfile string) (string, error) if absDockerfile == "" { // No -f/--file was specified so use the default relative to the // context directory. - absDockerfile = filepath.Join(absContextDir, DefaultDockerfileName) + absDockerfile = filepath.Join(absContextDir, defaultDockerfileName) // Just to be nice ;-) look for 'dockerfile' too but only // use it if we found it, otherwise ignore this check if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) { - altPath := filepath.Join(absContextDir, strings.ToLower(DefaultDockerfileName)) + altPath := filepath.Join(absContextDir, strings.ToLower(defaultDockerfileName)) if _, err = os.Lstat(altPath); err == nil { absDockerfile = altPath } diff --git a/cli/command/image/build/context_test.go b/cli/command/image/build/context_test.go index 8630d81146fe..05f4798ac3ee 100644 --- a/cli/command/image/build/context_test.go +++ b/cli/command/image/build/context_test.go @@ -31,7 +31,7 @@ func prepareNoFiles(t *testing.T) string { func prepareOneFile(t *testing.T) string { t.Helper() contextDir := createTestTempDir(t) - createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents) + createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents) return contextDir } @@ -66,7 +66,7 @@ func TestGetContextFromLocalDirNotExistingDockerfile(t *testing.T) { func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) { contextDir := createTestTempDir(t) - createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents) + createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents) chdir(t, contextDir) @@ -74,23 +74,23 @@ func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) { assert.NilError(t, err) assert.Check(t, is.Equal(contextDir, absContextDir)) - assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile)) + assert.Check(t, is.Equal(defaultDockerfileName, relDockerfile)) } func TestGetContextFromLocalDirWithDockerfile(t *testing.T) { contextDir := createTestTempDir(t) - createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents) + createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents) absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "") assert.NilError(t, err) assert.Check(t, is.Equal(contextDir, absContextDir)) - assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile)) + assert.Check(t, is.Equal(defaultDockerfileName, relDockerfile)) } func TestGetContextFromLocalDirLocalFile(t *testing.T) { contextDir := createTestTempDir(t) - createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents) + createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents) testFilename := createTestTempFile(t, contextDir, "tmpTest", "test") absContextDir, relDockerfile, err := GetContextFromLocalDir(testFilename, "") @@ -112,13 +112,13 @@ func TestGetContextFromLocalDirWithCustomDockerfile(t *testing.T) { contextDir := createTestTempDir(t) chdir(t, contextDir) - createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents) + createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents) - absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, DefaultDockerfileName) + absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, defaultDockerfileName) assert.NilError(t, err) assert.Check(t, is.Equal(contextDir, absContextDir)) - assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile)) + assert.Check(t, is.Equal(defaultDockerfileName, relDockerfile)) } func TestGetContextFromReaderString(t *testing.T) { @@ -150,8 +150,8 @@ func TestGetContextFromReaderString(t *testing.T) { t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents) } - if relDockerfile != DefaultDockerfileName { - t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile) + if relDockerfile != defaultDockerfileName { + t.Fatalf("Relative path not equals %s, got: %s", defaultDockerfileName, relDockerfile) } } @@ -164,12 +164,12 @@ func TestGetContextFromReaderStringConflict(t *testing.T) { func TestGetContextFromReaderTar(t *testing.T) { contextDir := createTestTempDir(t) - createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents) + createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents) tarStream, err := archive.Tar(contextDir, compression.None) assert.NilError(t, err) - tarArchive, relDockerfile, err := GetContextFromReader(tarStream, DefaultDockerfileName) + tarArchive, relDockerfile, err := GetContextFromReader(tarStream, defaultDockerfileName) assert.NilError(t, err) tarReader := tar.NewReader(tarArchive) @@ -177,8 +177,8 @@ func TestGetContextFromReaderTar(t *testing.T) { header, err := tarReader.Next() assert.NilError(t, err) - if header.Name != DefaultDockerfileName { - t.Fatalf("Dockerfile name should be: %s, got: %s", DefaultDockerfileName, header.Name) + if header.Name != defaultDockerfileName { + t.Fatalf("Dockerfile name should be: %s, got: %s", defaultDockerfileName, header.Name) } buff := new(bytes.Buffer) @@ -197,8 +197,8 @@ func TestGetContextFromReaderTar(t *testing.T) { t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents) } - if relDockerfile != DefaultDockerfileName { - t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile) + if relDockerfile != defaultDockerfileName { + t.Fatalf("Relative path not equals %s, got: %s", defaultDockerfileName, relDockerfile) } } @@ -223,7 +223,7 @@ func TestValidateContextDirectoryWithOneFile(t *testing.T) { } func TestValidateContextDirectoryWithOneFileExcludes(t *testing.T) { - testValidateContextDirectory(t, prepareOneFile, []string{DefaultDockerfileName}) + testValidateContextDirectory(t, prepareOneFile, []string{defaultDockerfileName}) } // createTestTempDir creates a temporary directory for testing. It returns the From a99e91cc8993caf48527ddf3cf24557c57702170 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 13 Oct 2025 15:23:50 +0200 Subject: [PATCH 189/193] cli/command/image/build: deprecate DetectArchiveReader util It was only used internal in the package. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit c52fa073cd14498d060f0d8d99e9909737b729b5) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build/context.go | 13 ++++++++++++- cli/command/image/build/context_test.go | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cli/command/image/build/context.go b/cli/command/image/build/context.go index 54085c12f822..ac4c226e94b3 100644 --- a/cli/command/image/build/context.go +++ b/cli/command/image/build/context.go @@ -105,7 +105,18 @@ func filepathMatches(matcher *patternmatcher.PatternMatcher, file string) (bool, // of input. If an archive is detected, ok is set to true, and to false // otherwise, in which case it is safe to assume input represents the contents // of a Dockerfile. +// +// Deprecated: this utility was only used internally, and will be removed in the next release. func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err error) { + return detectArchiveReader(input) +} + +// detectArchiveReader detects whether the input stream is an archive or a +// Dockerfile and returns a buffered version of input, safe to consume in lieu +// of input. If an archive is detected, ok is set to true, and to false +// otherwise, in which case it is safe to assume input represents the contents +// of a Dockerfile. +func detectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err error) { buf := bufio.NewReader(input) magic, err := buf.Peek(archiveHeaderSize * 2) @@ -146,7 +157,7 @@ func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) { // Dockerfile or tar archive. Returns a tar archive used as a context and a // path to the Dockerfile inside the tar. func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) { - rc, ok, err := DetectArchiveReader(rc) + rc, ok, err := detectArchiveReader(rc) if err != nil { return nil, "", err } diff --git a/cli/command/image/build/context_test.go b/cli/command/image/build/context_test.go index 05f4798ac3ee..b0c83a46b1d2 100644 --- a/cli/command/image/build/context_test.go +++ b/cli/command/image/build/context_test.go @@ -326,7 +326,7 @@ func TestDetectArchiveReader(t *testing.T) { _ = content.Close() }() - _, isArchive, err := DetectArchiveReader(content) + _, isArchive, err := detectArchiveReader(content) assert.NilError(t, err) assert.Check(t, is.Equal(tc.expected, isArchive), tc.file) }) From dd832b6d97929ece94c83834840424bb4c7e0570 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 13 Oct 2025 15:25:06 +0200 Subject: [PATCH 190/193] cli/command/image/build: deprecate WriteTempDockerfile util It was only used internal in the package. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 6e1ff0bec1c87913c3cb58e2499fbf549fb3221f) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build/context.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cli/command/image/build/context.go b/cli/command/image/build/context.go index ac4c226e94b3..b0248922be51 100644 --- a/cli/command/image/build/context.go +++ b/cli/command/image/build/context.go @@ -130,7 +130,16 @@ func detectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err er // WriteTempDockerfile writes a Dockerfile stream to a temporary file with a // name specified by defaultDockerfileName and returns the path to the // temporary directory containing the Dockerfile. +// +// Deprecated: this utility was only used internally, and will be removed in the next release. func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) { + return writeTempDockerfile(rc) +} + +// writeTempDockerfile writes a Dockerfile stream to a temporary file with a +// name specified by defaultDockerfileName and returns the path to the +// temporary directory containing the Dockerfile. +func writeTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) { // err is a named return value, due to the defer call below. dockerfileDir, err = os.MkdirTemp("", "docker-build-tempdockerfile-") if err != nil { @@ -175,7 +184,7 @@ func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadC return nil, "", errors.New("ambiguous Dockerfile source: both stdin and flag correspond to Dockerfiles") } - dockerfileDir, err := WriteTempDockerfile(rc) + dockerfileDir, err := writeTempDockerfile(rc) if err != nil { return nil, "", err } From 5dbaa5232582f477cfbfad052f1f22f99c6ab95c Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 13 Oct 2025 15:28:12 +0200 Subject: [PATCH 191/193] cli/command/image/build: deprecate ResolveAndValidateContextPath util This utility was used internally and will be removed in the next release. Use `DetectContextType` to detect the context-type, and use `GetContextFromLocalDir`, `GetContextFromLocalDir`, `GetContextFromGitURL`, or `GetContextFromURL` instead. Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 0f2f9e9c41f10997aad8c370fe1193d7a744a3ac) Signed-off-by: Sebastiaan van Stijn --- cli/command/image/build/context.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/cli/command/image/build/context.go b/cli/command/image/build/context.go index b0248922be51..3c7065b9b6b8 100644 --- a/cli/command/image/build/context.go +++ b/cli/command/image/build/context.go @@ -234,7 +234,7 @@ func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error) return "", "", errors.Wrapf(err, "unable to 'git clone' to temporary context directory") } - absContextDir, err = ResolveAndValidateContextPath(absContextDir) + absContextDir, err = resolveAndValidateContextPath(absContextDir) if err != nil { return "", "", err } @@ -287,7 +287,7 @@ func getWithStatusError(url string) (resp *http.Response, err error) { // the relative path of the dockerfile in that context directory, and a non-nil // error on success. func GetContextFromLocalDir(localDir, dockerfileName string) (string, string, error) { - localDir, err := ResolveAndValidateContextPath(localDir) + localDir, err := resolveAndValidateContextPath(localDir) if err != nil { return "", "", err } @@ -307,7 +307,18 @@ func GetContextFromLocalDir(localDir, dockerfileName string) (string, string, er // ResolveAndValidateContextPath uses the given context directory for a `docker build` // and returns the absolute path to the context directory. +// +// Deprecated: this utility was used internally and will be removed in the next +// release. Use [DetectContextType] to detect the context-type, and use +// [GetContextFromLocalDir], [GetContextFromLocalDir], [GetContextFromGitURL], +// or [GetContextFromURL] instead. func ResolveAndValidateContextPath(givenContextDir string) (string, error) { + return resolveAndValidateContextPath(givenContextDir) +} + +// resolveAndValidateContextPath uses the given context directory for a `docker build` +// and returns the absolute path to the context directory. +func resolveAndValidateContextPath(givenContextDir string) (string, error) { absContextDir, err := filepath.Abs(givenContextDir) if err != nil { return "", errors.Errorf("unable to get absolute context directory of given context directory %q: %v", givenContextDir, err) From 7494d2cee4da380e9acf2c836989c1f29252f2cc Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 3 Nov 2025 13:57:01 +0100 Subject: [PATCH 192/193] cli: allManagementSubCommands: improve handling of plugin stubs The allManagementSubCommands function is used to present plugin-commands in the docker --help output; these commands are included in the "management commands" section, but for plugins we don't know if they have sub-commands. However, plugin stubs may be hidden (for placeholders that are not yet loaded), or not be runnable, which was previously ignored. This patch treats plugin-stubs the same as other commands, with the exception of checking if they have subcommands (which is not yet known for plugin-stubs). Signed-off-by: Sebastiaan van Stijn (cherry picked from commit 259df25a9663f7fe08b7a4ae0456fa4b80065303) Signed-off-by: Sebastiaan van Stijn --- cli/cobra.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cli/cobra.go b/cli/cobra.go index a75a66e66039..793d6b0b1f08 100644 --- a/cli/cobra.go +++ b/cli/cobra.go @@ -376,13 +376,10 @@ func orchestratorSubCommands(cmd *cobra.Command) []*cobra.Command { func allManagementSubCommands(cmd *cobra.Command) []*cobra.Command { cmds := []*cobra.Command{} for _, sub := range cmd.Commands() { - if isPlugin(sub) { - if invalidPluginReason(sub) == "" { - cmds = append(cmds, sub) - } + if invalidPluginReason(sub) != "" { continue } - if sub.IsAvailableCommand() && sub.HasSubCommands() { + if sub.IsAvailableCommand() && (isPlugin(sub) || sub.HasSubCommands()) { cmds = append(cmds, sub) } } From c475c696cfd8f7ddfa4765c32c436d9746780d9d Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:51:18 +0100 Subject: [PATCH 193/193] Plugin may set itself as hidden Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> (cherry picked from commit 700875b666e585dc3b722078272e806aa2a8f4c9) Signed-off-by: Sebastiaan van Stijn --- cli-plugins/manager/cobra.go | 1 + cli-plugins/metadata/metadata.go | 2 ++ cli/cobra_test.go | 27 +++++++++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/cli-plugins/manager/cobra.go b/cli-plugins/manager/cobra.go index ddf067be1dbd..57ec4a7909ec 100644 --- a/cli-plugins/manager/cobra.go +++ b/cli-plugins/manager/cobra.go @@ -38,6 +38,7 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e rootCmd.AddCommand(&cobra.Command{ Use: p.Name, Short: p.ShortDescription, + Hidden: p.Hidden, Run: func(_ *cobra.Command, _ []string) {}, Annotations: annotations, DisableFlagParsing: true, diff --git a/cli-plugins/metadata/metadata.go b/cli-plugins/metadata/metadata.go index 9d408c00b3dd..7061486a7056 100644 --- a/cli-plugins/metadata/metadata.go +++ b/cli-plugins/metadata/metadata.go @@ -33,4 +33,6 @@ type Metadata struct { ShortDescription string `json:",omitempty"` // URL is a pointer to the plugin's homepage. URL string `json:",omitempty"` + // Hidden hides the plugin in completion and help message output. + Hidden bool `json:",omitempty"` } diff --git a/cli/cobra_test.go b/cli/cobra_test.go index 9ca87ea5c836..eb87c75d4184 100644 --- a/cli/cobra_test.go +++ b/cli/cobra_test.go @@ -56,6 +56,33 @@ func TestInvalidPlugin(t *testing.T) { assert.DeepEqual(t, invalidPlugins(root), []*cobra.Command{sub1}, cmpopts.IgnoreUnexported(cobra.Command{})) } +func TestHiddenPlugin(t *testing.T) { + root := &cobra.Command{Use: "root"} + sub1 := &cobra.Command{ + Use: "sub1", + Hidden: true, + Annotations: map[string]string{ + metadata.CommandAnnotationPlugin: "true", + }, + Run: func(cmd *cobra.Command, args []string) {}, + } + + sub1sub1 := &cobra.Command{Use: "sub1sub1"} + sub1sub2 := &cobra.Command{Use: "sub1sub2"} + sub2 := &cobra.Command{ + Use: "sub2", + Annotations: map[string]string{ + metadata.CommandAnnotationPlugin: "true", + }, + Run: func(cmd *cobra.Command, args []string) {}, + } + + root.AddCommand(sub1, sub2) + sub1.AddCommand(sub1sub1, sub1sub2) + + assert.DeepEqual(t, allManagementSubCommands(root), []*cobra.Command{sub2}, cmpopts.IgnoreFields(cobra.Command{}, "Run"), cmpopts.IgnoreUnexported(cobra.Command{})) +} + func TestCommandAliases(t *testing.T) { root := &cobra.Command{Use: "root"} sub := &cobra.Command{Use: "subcommand", Aliases: []string{"alias1", "alias2"}}