Compare commits

...

137 Commits

Author SHA1 Message Date
578ccf607d Merge pull request #6170 from thaJeztah/e2e_newline_check
Some checks failed
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 23, local) (push) Has been cancelled
e2e / tests (alpine, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 26, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 23, local) (push) Has been cancelled
e2e / tests (debian, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 26, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-13) (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
e2e/global: TestPromptExitCode: check for trailing newline
2025-07-09 14:04:02 +02:00
0c5e258f8a e2e/global: TestPromptExitCode: check for trailing newline
Make the test slightly more permissive; we're looking for a trailing
newline, not necessarily an empty line.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-09 13:35:17 +02:00
30cad385b6 Merge pull request #6167 from vvoland/update-go
Update to go1.24.5
2025-07-09 01:23:29 +02:00
9bcc88611f update to go1.24.5
- https://github.com/golang/go/issues?q=milestone%3AGo1.24.5+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.24.4...go1.24.5

This minor releases include 1 security fixes following the security policy:

- cmd/go: unexpected command execution in untrusted VCS repositories

    Various uses of the Go toolchain in untrusted VCS repositories can result in
    unexpected code execution. When using the Go toolchain in directories fetched
    using various VCS tools (such as directly cloning Git or Mercurial repositories)
    can cause the toolchain to execute unexpected commands, if said directory
    contains multiple VCS configuration metadata (such as a '.hg' directory in a Git
    repository). This is due to how the Go toolchain attempts to resolve which VCS
    is being used in order to embed build information in binaries and determine
    module versions.

    The toolchain will now abort attempting to resolve which VCS is being used if it
    detects multiple VCS configuration metadata in a module directory or nested VCS
    configuration metadata (such as a '.git' directoy in a parent directory and a
    '.hg' directory in a child directory). This will not prevent the toolchain from
    building modules, but will result in binaries omitting VCS related build
    information.

    If this behavior is expected by the user, the old behavior can be re-enabled by
    setting GODEBUG=allowmultiplevcs=1. This should only be done in trusted
    repositories.

    Thanks to RyotaK (https://ryotak.net) of GMO Flatt Security Inc for reporting
    this issue.

    This is CVE-2025-4674 and https://go.dev/issue/74380.

View the release notes for more information:
https://go.dev/doc/devel/release#go1.24.5

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-07-08 19:23:57 +02:00
3302212263 Merge pull request #6163 from Benehiko/env-credential-warn
registry: warn of `DOCKER_AUTH_CONFIG` usage in login and logout
2025-07-08 15:33:07 +02:00
ccd5bd8d57 registry: warn of DOCKER_AUTH_CONFIG usage in login and logout
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-07-08 14:07:32 +02:00
dec07e6fdf tui/note: add warning note type
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-07-08 14:07:22 +02:00
28f19a9d65 Merge pull request #6162 from ArthurFlag/ENGDOCS-2807-cdi-docs-update
docs: cdi isn't experimental
2025-07-07 17:53:48 +02:00
219e5ca4f2 Merge pull request #6165 from thaJeztah/bump_engine_28.3.1
vendor: github.com/docker/docker v28.3.1
2025-07-07 17:53:31 +02:00
7e040d91ef docs: cdi is not experimental anymore
Signed-off-by: ArthurFlag <arthur.flageul@docker.com>
2025-07-07 16:10:30 +02:00
76524e7d0e vendor: github.com/docker/docker v28.3.1
no changes in vendored code

full diff: https://github.com/docker/docker/compare/v28.3.0...v28.3.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-07 15:52:29 +02:00
3262107821 cli/config: export const dockerEnvConfig
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-07-04 14:04:38 +02:00
8403869122 Merge pull request #6158 from thaJeztah/reduce_strslice
cli/command/container: remove redundant uses of strslice.StrSlice
2025-07-02 17:42:43 +02:00
1fc7194554 Merge pull request #6159 from thaJeztah/hide_codecov
rename codecov.yml to .codecov.yml
2025-07-02 17:42:11 +02:00
fa2a7f1536 Merge pull request #6154 from thaJeztah/bump_engine
vendor: github.com/docker/docker v28.3.0
2025-07-02 17:41:49 +02:00
350b3a6e25 Merge pull request #6160 from thaJeztah/fix_otel_debug_logs
cli/debug: fix OTELErrorHandler logging messages if there's no error
2025-07-02 17:41:26 +02:00
4ea6fbf538 cli/debug: fix OTELErrorHandler logging messages if there's no error
I noticed this in a ticket in the compose issue tracker; with debug logging
enabled, the OTEL error-logger may be logging even if there's no error;

    DEBU[0000] Executing bake with args: [bake --file - --progress rawjson --metadata-file /tmp/compose-build-metadataFile-1203980021.json --allow fs.read=/home/user/dev/project --allow fs.read=/home/user/dev/project --allow fs.read=/home/user/dev/project/nginx --allow fs.read=/home/user/dev/project]
    TRAC[0000] Plugin server listening on @docker_cli_d8df486f78df3b7357995be71bf0cef6
    DEBU[0005] otel error                                    error="<nil>"
    ^CTRAC[0055] Closing plugin server
    TRAC[0055] Closing plugin server
    DEBU[0055] otel error                                    error="<nil>"
    DEBU[0055] otel error                                    error="<nil>"

Update the error-handler to not log if there's no error.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-02 11:59:05 +02:00
74a896f18c Merge pull request #6157 from ndeloof/use_api_socket
mount /var/run/docker.sock for --use-api-socket
2025-07-01 17:00:24 +02:00
94f097da28 rename codecov.yml to .codecov.yml
Make it a hidden file. From the [CodeCov docs][1]:

> Can I name the file .codecov.yml?
>
> Yes, you can name the file `codecov.yml` or `.codecov.yml`. However, the
> file must still be located in the repository root, `dev/`, or `.github/`
> directories

[1]: https://docs.codecov.com/docs/codecov-yaml#can-i-name-the-file-codecovyml

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-01 16:20:54 +02:00
e7e238eb4b cli/command/container: remove redundant uses of strslice.StrSlice
The strslice.StrSlice type is a string-slice with a custom JSON Unmarshal
function to provide backward-compatibility with older API requests (see
[moby@17d6f00] and [moby@ea4a067]).

Given that the type is assigned implicitly through the fields on HostConfig,
we can just use a regular []string instead.

[moby@17d6f00]: 17d6f00ec2
[moby@ea4a067]: ea4a06740b

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-01 10:09:54 +02:00
2ba7cb8b44 mount /var/run/docker.sock for --use-api-socket
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-06-30 12:38:15 +02:00
52e1e4fb21 vendor: github.com/docker/docker v28.3.0
no diff; same commit: https://github.com/docker/docker/compare/v28.3.0-rc.2...v28.3.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-30 09:35:21 +02:00
7cbee73f19 Merge pull request #6147 from thaJeztah/connhelper_quote
cli/connhelper: quote ssh arguments to prevent shell injection
2025-06-25 17:21:12 +02:00
ae6f8d0021 Merge pull request #6149 from vvoland/gha-tags
gha/bin-image: add major and minor version image tags
2025-06-25 14:35:11 +00:00
70867e7067 gha/bin-image: add major and minor version image tags
Adding image tags that follow the semver major and minor versions (e.g., `28`
and `28.3`) for the moby-bin images.

This makes it easier for users to reference the latest build within a
major or minor version series without having to know the exact
minor/patch version.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-06-24 23:35:10 +02:00
38b7060a21 Merge pull request #6148 from thaJeztah/vendor_rc2
Some checks failed
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 23, local) (push) Has been cancelled
e2e / tests (alpine, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 26, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 23, local) (push) Has been cancelled
e2e / tests (debian, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 26, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-13) (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
vendor: github.com/docker/docker v28.3.0-rc.2
2025-06-24 15:37:19 +00:00
2d46d162c1 vendor: github.com/docker/docker v28.3.0-rc.2
no diff; same commit, but tagged;
https://github.com/docker/docker/compare/265f70964794...v28.3.0-rc.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-24 16:35:29 +02:00
88d1133224 cli/connhelper: quote ssh arguments to prevent shell injection
When connecting to a remote daemon through an ssh:// connection,
the CLI connects with the remote host using ssh, executing the
`docker system dial-stdio` command on the remote host to connect
to the daemon API's unix socket.

By default, the `docker system dial-stdio` command connects with the
daemon using the default location (/var/run/docker.sock), or the
location as configured on the remote host.

Commit 25ebf0ec9c (included in docker
CLI v24.0.0-rc.2 and higher) introduced a feature to allow the location
of the socket to be specified through the host connection string, for
example:

     DOCKER_HOST='ssh://example.test/run/custom-docker.sock'

The custom path is included as part of the ssh command executed from
the client machine to connect with the remote host. THe example above
would execute the following command from the client machine;

    ssh -o ConnectTimeout=30 -T -- example.test docker --host unix:///run/custom-docker.sock system dial-stdio

ssh executes remote commands in a shell environment, and no quoting
was in place, which allowed for a connection string to include additional
content, which would be expanded / executed on the remote machine.

For example, the following example would execute `echo hello > /hello.txt`
on the remote machine;

    export DOCKER_HOST='ssh://example.test/var/run/docker.sock $(echo hello > /hello.txt)'
    docker info
    # (output of docker info from the remote machine)

While this doesn't allow the user to do anything they're not already
able to do so (by directly using the same SSH connection), the behavior
is not expected, so this patch adds quoting to prevent such URLs from
resulting in expansion.

This patch updates the cli/connhelper and cli/connhelper/ssh package to
quote parameters used in the ssh command to prevent code execution and
expansion of variables on the remote machine. Quoting is also applied to
other parameters that are obtained from the DOCKER_HOST url, such as username
and hostname.

- The existing `Spec.Args()` method inthe cli/connhelper/ssh package now
  quotes arguments, and returns a nil slice when failing to quote. Users
  of this package should therefore check the returned arguments before
  consuming. This  method did not provide an error-return, and adding
  one would be a breaking change.
- A new `Spec.Command` method is introduced, which (unlike the `Spec.Args()`
  method) provides an error return. Users are recommended to use this new
  method instead of the `Spec.Args()` method.

Some minor additional changes in behavior are included in this patch;

- Connection URLs with a trailing slash (e.g. `ssh://example.test/`)
  would previously result in `unix:///` being used as custom socket
  path. After this patch, the trailing slash is ignored, and no custom
  socket path is used.
- Specifying a remote command is now required. When passing an empty
  remote command, `Spec.Args()` now results in a `nil` value to be
  returned (or an `no remote command specified` error when using
  `Spec.Comnmand()`.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-24 16:26:17 +02:00
82eda48066 cli/connhelper/internal/syntax: fix linting issues
cli/connhelper/internal/syntax/parser.go:31:2: Duplicate words (the) found (dupword)
        // Note that it shares some features with Bash, due to the the shared
        ^
    cli/connhelper/internal/syntax/quote.go:48:1: cyclomatic complexity 35 of func `Quote` is high (> 16) (gocyclo)
    func Quote(s string, lang LangVariant) (string, error) {
    ^
    cli/connhelper/internal/syntax/quote.go:103:3: shadow: declaration of "offs" shadows declaration at line 56 (govet)
            offs := 0
            ^

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-24 16:24:34 +02:00
52d2a9b5ae cli/connhelper/internal/syntax: remove unused code from fork
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-24 16:24:29 +02:00
64a9a6d0c8 cli/connhelper: add fork of mvdan.cc/sh/v3/syntax v3.10.0
This adds a local fork of the mvdan.cc/sh/v3/syntax package to provide the
Quote function without having to introduce additional (indirect) dependencies
of the mvdan.cc/sh module.

This commit does not compile as it references code not forked.

The following files were included:

- https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/syntax/quote.go
- https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/syntax/parser.go
- https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/LICENSE

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-24 10:02:53 +02:00
f03fb6c40b Merge pull request #6146 from thaJeztah/bump_docker
Some checks failed
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 23, local) (push) Has been cancelled
e2e / tests (alpine, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 26, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 23, local) (push) Has been cancelled
e2e / tests (debian, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 26, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-13) (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
vendor: github.com/docker/docker 265f70964794 (v28.3.0-rc.2)
2025-06-20 18:33:20 +02:00
5bb0d7f70c vendor: github.com/docker/docker 265f70964794 (v28.3.0-rc.2)
full diff: https://github.com/docker/docker/compare/v28.3.0-rc.1...265f709647947fb5a1adf7e4f96f2113dcc377bd

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-20 18:25:22 +02:00
575d4af72f vendor: github.com/docker/docker v28.3.0-rc.1
no diff: just tagged; https://github.com/docker/docker/compare/6a1fb46d4805...v28.3.0-rc.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-20 17:43:37 +02:00
4b202b9e2b Merge pull request #6141 from thaJeztah/login_no_tty
prevent login prompt on registry operations with no TTY attached
2025-06-20 12:40:36 +02:00
80d1959ee3 Merge pull request #6144 from thaJeztah/rm_top_level_remove
remove undocumented top-level "docker remove" command
2025-06-20 06:14:15 +09:00
19a5c5c714 remove undocumented top-level "docker remove" command
This was introduced in 9b54d860cd,
which added `docker container remove` as alias for `docker container rm`.

However, due to the `NewRmCommand` being used both for adding the top-level
`docker rm` command and for adding the `docker container rm` command, it
also introduced a (hidden) top-level `docker remove` command;

    docker remove --help | head -n1
    Usage:  docker rm [OPTIONS] CONTAINER [CONTAINER...]

The command was not documented, and did not appear in `--help` output,
nor was auto-complete provided;

    docker --help | grep remove

    docker r<TAB>
    rename               (Rename a container)  rm  (Remove one or more containers)  run  (Create and run a new container from an image)
    restart  (Restart one or more containers)  rmi     (Remove one or more images)

This patch adds a dedicated, non-exported `newRemoveCommand` to add sub-
commands for `docker container`, taking a similar approach as was done in
[moby@b993609d5a] for `docker image rm`.

With this patch applied, the hidden command is no longer there, but
the `docker rm`, `docker container rm`, and `docker container remove`
commands stay functional as intended;

    docker remove foo
    docker: unknown command: docker remove

    Run 'docker --help' for more information

    docker rm --help | head -n1
    Usage:  docker rm [OPTIONS] CONTAINER [CONTAINER...]
    docker container rm --help | head -n1
    Usage:  docker container rm [OPTIONS] CONTAINER [CONTAINER...]
    docker container remove --help | head -n1
    Usage:  docker container rm [OPTIONS] CONTAINER [CONTAINER...]

[moby@b993609d5a]: b993609d5a

Reported-by: Lorenzo Buero <138243046+LorenzoBuero@users.noreply.github.com>
Co-authored-by: Lorenzo Buero <138243046+LorenzoBuero@users.noreply.github.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-19 15:42:54 +02:00
c88268681e prevent login prompt on registry operations with no TTY attached
When pulling or pushing images, the CLI could prompt for a password
if the push/pull failed and the registry returned a 401 (Unauthorized)

Ironically, this feature did not work when using Docker Hub (and possibly
other registries using basic auth), due to some custom error handling added
in [moby@19a93a6e3d42], which also discards the registry's status code,
changing it to a 404;

    curl -v -XPOST --unix-socket /var/run/docker.sock 'http://localhost/v1.50/images/create?fromImage=docker.io%2Fexample%2Fprivate&tag=latest'
    ...
    < HTTP/1.1 404 Not Found
    < Content-Type: application/json
    ...
    {"message":"pull access denied for example/private, repository does not exist or may require 'docker login'"}

And due to a bug, other registries (not using basic auth) returned a generic
error, which resulted in a 500 Internal Server Error. That bug was fixed in
docker 28.2, now returning the upstream status code and trigger an interactive
prompt;

    docker pull icr.io/my-ns/my-image:latest
    Please login prior to pull:
    Username:

This prompt would be triggered unconditionally, also if the CLI was run
non-interactively and no TTY attached;

    docker pull icr.io/my-ns/my-image:latest < /dev/null
    Please login prior to pull:
    Username:

With this PR, no prompt is shown ;

    # without STDIN attached
    docker pull icr.io/my-ns/my-image:latest < /dev/null
    Error response from daemon: error from registry: Authorization required. See https://cloud.ibm.com/docs/Registry?topic=Registry-troubleshoot-auth-req - Authorization required. See https://cloud.ibm.com/docs/Registry?topic=Registry-troubleshoot-auth-req

For now, the prompt is still shown otherwise;

    docker pull icr.io/my-ns/my-image:latest

    Login prior to pull:
    Username: ^C

[moby@19a93a6e3d42]: 19a93a6e3d

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-19 10:25:27 +02:00
747cb4448f Merge pull request #6140 from vvoland/image-tree-used
image/tree: Fix top image chip detection
2025-06-18 20:13:03 +00:00
23fe9ec244 image/tree: Fix top image chip detection
Currently, image tree visualization doesn't properly detect chips for
parent images, only looking at child images. This patch fixes the issue
by checking both parent and child images when determining which chips to
display in the tree view.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-06-18 20:18:58 +02:00
51025e12e5 Merge pull request #6008 from Benehiko/env-credentials-store
Use `DOCKER_AUTH_CONFIG` env as credential store
2025-06-18 18:07:07 +00:00
9b83d5bbf9 Use DOCKER_AUTH_CONFIG env as credential store
This patch enables the CLI to natively pick up the `DOCKER_AUTH_CONFIG`
environment variable and use it as a credential store.

The `DOCKER_AUTH_CONFIG` value should be a JSON object and must store
the credentials in a base64 encoded string under the `auth` key.
Specifying additional fields will cause the parser to fail.

For example:
`printf "username:pat" | openssl base64 -A`

`export DOCKER_AUTH_CONFIG='{
  "auths": {
    "https://index.docker.io/v1/": {
      "auth": "aGk6KTpkY2tyX3BhdF9oZWxsbw=="
    }
  }
}'`

Credentials stored in `DOCKER_AUTH_CONFIG` would take precedence over any
credential stored in the file store (`~/.docker/config.json`) or native store
(credential helper).

Destructive actions, such as deleting a credential would result in a noop if
found in the environment credential. Credentials found in the file or
native store would get removed.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-06-18 18:55:42 +02:00
ab2d683f61 Merge pull request #6137 from thaJeztah/execconfig_detach
cli/command/container: remove use of ExecOptions.Detach as intermediate
2025-06-17 14:22:28 +02:00
3664c08b73 Merge pull request #6138 from thaJeztah/bump_swarmkit
vendor: github.com/moby/swarmkit/v2 v2.0.0
2025-06-17 14:21:47 +02:00
cccf6d8cc4 vendor: github.com/moby/swarmkit/v2 v2.0.0
full diff: https://github.com/moby/swarmkit/compare/8c1959736554...v2.0.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-17 13:26:30 +02:00
50da0ad9df cli/command/container: remove use of ExecOptions.Detach as intermediate
This field was added in [moby@5130fe5d38837302e], which
added it for use as intermediate struct when parsing CLI flags (through
`runconfig.ParseExec`) in [moby@c786a8ee5e9db8f5f].

Commit [moby@9d9dff3d0d9e92adf] rewrote the CLI to use
Cobra, and as part of this introduced a separate `execOptions` type in
`api/client/container`, however the ExecOptions.Detach field was still
used as intermediate field to store the flag's value.

Given that the client doesn't use this field, let's remove its use to
prevent giving the impression that it's used anywhere.

[moby@5130fe5d38837302e]: 5130fe5d38
[moby@c786a8ee5e9db8f5f]: c786a8ee5e
[moby@9d9dff3d0d9e92adf]: 9d9dff3d0d

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-17 12:53:44 +02:00
dbb5872b69 Merge pull request #6135 from thaJeztah/fix_login_message
cli/command: RegistryAuthenticationPrivilegedFunc: fix hints for login
2025-06-16 14:35:56 +02:00
e2632c5c4f cli/command: RegistryAuthenticationPrivilegedFunc: fix hints for login
The RegistryAuthenticationPrivilegedFunc has some conditional logic to
add additional hints when logging in to the default (Docker Hub) registry.
Commit 9f4165ccb8 inadvertently passed the
wrong variable to PromptUserForCredentials, which caused it to show the
additional hints for Docker Hub.

Before this patch, hints were printed for the default (docker hub) registry;

    docker pull icr.io/my-ns/my-image:latest

    Login prior to pull:
    Log in with your Docker ID or email address to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com/ to create one.
    You can log in with your password or a Personal Access Token (PAT). Using a limited-scope PAT grants better security and is required for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/

    Username:

With this patch, those hints are omitted;

    docker pull icr.io/my-ns/my-image:latest

    Login prior to pull:
    Username:

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-16 12:14:53 +02:00
f53bb8882f Merge pull request #6131 from vvoland/vendor-docker
Some checks failed
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 23, local) (push) Has been cancelled
e2e / tests (alpine, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 26, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 23, local) (push) Has been cancelled
e2e / tests (debian, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 26, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-13) (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
vendor: github.com/docker/docker v28.3.0-dev (6a1fb46d4805)
2025-06-13 16:29:11 +00:00
4cb0695b49 vendor: github.com/docker/docker v28.3.0-dev (6a1fb46d4805)
full diff: https://github.com/docker/docker/compare/v28.2.2...6a1fb46d4805

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-06-13 18:23:51 +02:00
3ce5130af6 Merge pull request #6132 from thaJeztah/replace_evt
cli/command/container: replace uses of deprecated event.Status field
2025-06-13 16:23:08 +00:00
99d4d1f386 cli/command/container: replace uses of deprecated event.Status field
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-13 18:18:28 +02:00
398fa5aa70 Merge pull request #6130 from thaJeztah/bump_version
bump version to v28.3.0-dev
2025-06-13 13:42:56 +00:00
e225d51919 bump version to v28.3.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.

d48fb9f9f7/docker.Makefile (L22)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-13 14:11:56 +02:00
9cc2a2bf2d Merge pull request #6129 from vvoland/docs-deprecated
docs: deprecate empty Config fields in image inspect API
2025-06-13 14:10:48 +02:00
181563ee99 docs: deprecate empty Config fields in image inspect API
Image config fields like Cmd, Entrypoint, Env, etc. will be omitted from
/images/{name}/json response when empty, starting in v29.0.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-06-13 13:48:07 +02:00
082d23d12d Merge pull request #6127 from thaJeztah/bump_deps
vendor: update buildkit and containerd dependencies
2025-06-12 13:20:31 +00:00
59e34093bc vendor: otel v1.35.0, otel/contrib v0.60.0, grpc v1.72.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-12 14:16:23 +02:00
a76643bca3 vendor: github.com/prometheus/client_golang v1.22.0
full diff: https://github.com/prometheus/client_golang/compare/v1.20.5...v1.22.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-12 14:12:33 +02:00
f6985b7a27 vendor: google.golang.org/protobuf v1.36.6
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-12 14:11:07 +02:00
bab8478ef3 vendor: golang.org/x/sys v0.33.0
full diff: https://github.com/golang/sys/compare/v0.32.0...v0.33.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-12 14:09:24 +02:00
9f82d4a791 vendor: golang.org/x/sync v0.14.0
full diff: https://github.com/golang/sync/compare/v0.13.0...v0.14.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-12 13:54:57 +02:00
8b8f558b83 Merge pull request #6124 from vvoland/update-go
update to go1.24.4
2025-06-10 15:12:26 +02:00
5487986681 Merge pull request #6123 from ndeloof/pluginserver
only close plugin server if actually created
2025-06-10 15:12:06 +02:00
b9c563a581 only close plugin server if actually created
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-06-10 14:57:19 +02:00
fe7fc2ff7f update to go1.24.4
- https://github.com/golang/go/issues?q=milestone%3AGo1.24.4+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.24.3...go1.24.4

This release includes 3 security fixes following the security policy:

- net/http: sensitive headers not cleared on cross-origin redirect

    Proxy-Authorization and Proxy-Authenticate headers persisted on cross-origin redirects potentially leaking sensitive information.

    Thanks to Takeshi Kaneko (GMO Cybersecurity by Ierae, Inc.) for reporting this issue.

    This is CVE-2025-4673 and Go issue https://go.dev/issue/73816.

- os: inconsistent handling of O_CREATE|O_EXCL on Unix and Windows

    os.OpenFile(path, os.O_CREATE|O_EXCL) behaved differently on Unix and Windows systems when the target path was a dangling symlink. On Unix systems, OpenFile with O_CREATE and O_EXCL flags never follows symlinks. On Windows, when the target path was a symlink to a nonexistent location, OpenFile would create a file in that location.

    OpenFile now always returns an error when the O_CREATE and O_EXCL flags are both set and the target path is a symlink.

    Thanks to Junyoung Park and Dong-uk Kim of KAIST Hacking Lab for discovering this issue.

    This is CVE-2025-0913 and Go issue https://go.dev/issue/73702.

- crypto/x509: usage of ExtKeyUsageAny disables policy validation

    Calling Verify with a VerifyOptions.KeyUsages that contains ExtKeyUsageAny unintentionally disabledpolicy validation. This only affected certificate chains which contain policy graphs, which are rather uncommon.

    Thanks to Krzysztof Skrzętnicki (@Tener) of Teleport for reporting this issue.

    This is CVE-2025-22874 and Go issue https://go.dev/issue/73612.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-06-09 16:25:41 +02:00
9e506545fd Merge pull request #6120 from thaJeztah/fix_url
docs: fix link to live-restore
2025-06-02 13:02:40 +02:00
3c1bbfd82f docs: fix link to live-restore
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-02 12:56:04 +02:00
0bfd4c9f29 Merge pull request #6119 from thaJeztah/bump_engine
vendor: github.com/docker/docker v28.2.2
2025-06-02 10:15:52 +02:00
d8f09a1b75 Merge pull request #6117 from vvoland/binimage-nosha
gha/bin-image: Don't push sha tags
2025-05-30 17:42:22 +02:00
473b248260 vendor: github.com/docker/docker v28.2.2
no diff; same commit, but tagged:
https://github.com/docker/docker/compare/45873be4ae3f...v28.2.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-30 17:37:57 +02:00
b2d63d17af gha/bin-image: Don't push sha tags
This change eliminates the automatic creation of image tags in the
format `dockereng/cli-bin:sha-ad132f5` for every push.

They're not too useful, produce noise and use a lot of space.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-05-30 12:00:29 +02:00
e6534b4eb7 Merge pull request #6116 from vvoland/vendor-docker
Some checks failed
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 23, local) (push) Has been cancelled
e2e / tests (alpine, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 26, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 23, local) (push) Has been cancelled
e2e / tests (debian, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 26, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-13) (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
vendor: github.com/docker/docker v28.2.2-dev (45873be4ae3f)
2025-05-30 09:39:08 +00:00
5c3128e95e vendor: github.com/docker/docker v28.2.2-dev (45873be4ae3f)
full diff: 0e2cc22d36...45873be4ae

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-05-30 11:15:35 +02:00
879ac3f88f Merge pull request #6110 from thaJeztah/bump_engine
Some checks failed
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 23, local) (push) Has been cancelled
e2e / tests (alpine, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 26, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 23, local) (push) Has been cancelled
e2e / tests (debian, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 26, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-13) (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
vendor: github.com/docker/docker 0e2cc22d36ae (v28.2-dev)
2025-05-28 13:17:56 +00:00
92fa1e1fc9 vendor: github.com/docker/docker 0e2cc22d36ae (v28.2-dev)
no changes in vendored code

full diff: 26db31fdab...0e2cc22d36

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-28 15:05:58 +02:00
4bec3a6795 Merge pull request #6114 from thaJeztah/deprecate_non_compliant_registries
docs: deprecated: fallback for non-OCI-compliant registries is removed
2025-05-28 14:28:52 +02:00
a007d1ae24 Merge pull request #6113 from thaJeztah/config_suppress_err
cli/config/configfile: explicitly ignore error
2025-05-28 12:08:37 +00:00
bbfbd54f4d docs: deprecated: fallback for non-OCI-compliant registries is removed
GitHub deprecated the legacy registry, and it was [sunset on Feb 24th, 2025][1]
in favor of GitHub Container Registry (GHCR) (ghcr.io), so the fallback
was removed.

[1]: https://github.blog/changelog/2025-01-23-legacy-docker-registry-closing-down/

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-28 10:05:40 +02:00
2d21e1f7a5 cli/config/configfile: explicitly ignore error
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-28 09:55:33 +02:00
bc9be0bdea Merge pull request #6112 from thaJeztah/bump_tools
Dockerfile: bump buildx v0.24.0, compose v2.36.2
2025-05-28 09:09:24 +02:00
3fe7dc5cb4 Dockerfile: update compose to v2.36.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-27 22:50:20 +02:00
9eae2a8976 Dockerfile: update buildx to v0.24.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-27 22:49:34 +02:00
a29e53dab7 Merge pull request #6111 from thaJeztah/update_deprecations
docs: deprecated: update status for non-standard fields in image inspect
2025-05-27 16:57:52 +02:00
da0c976fb0 docs: deprecated: update status for non-standard fields in image inspect
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-27 16:14:00 +02:00
17dc2880fa Merge pull request #6109 from thaJeztah/image_rm_platform
image rm: add --platform option
2025-05-27 12:11:26 +02:00
bb0ca9f9ef image rm: add --platform option
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-27 10:58:11 +02:00
f567263802 Merge pull request #6099 from thaJeztah/bump_engine
vendor: github.com/docker/docker 26db31fdab62 (v28.2-dev)
2025-05-27 10:49:43 +02:00
7775f01caa vendor: github.com/docker/docker 26db31fdab62 (v28.2-dev)
full diff: https://github.com/docker/docker/compare/v28.2.0-rc.2...26db31fdab628a2345ed8f179e575099384166a9

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-27 09:20:06 +02:00
908ff04d6e Merge pull request #6108 from thaJeztah/bump_engine_28.2
vendor: github.com/docker/docker v28.2.0-rc.2
2025-05-26 13:17:15 +00:00
519bc2daa1 vendor: github.com/docker/docker v28.2.0-rc.2
no changes in vendored code

full diff: https://github.com/docker/docker/compare/f4ffeb8c38b3...v28.2.0-rc.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-26 14:06:52 +02:00
b2c4452acb Merge pull request #6101 from thaJeztah/deprecated_api_versions_removed
Some checks failed
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 23, local) (push) Has been cancelled
e2e / tests (alpine, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 26, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 23, local) (push) Has been cancelled
e2e / tests (debian, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 26, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-13) (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
docs: deprecated: mark API < v1.24 as "removed"
2025-05-22 12:09:34 +00:00
3e287df661 Merge pull request #6100 from thaJeztah/fluentd_async_connect_removed
docs: deprecated: mark `fluentd-async-connect` as "removed"
2025-05-22 14:09:24 +02:00
f1fb3e3011 Merge pull request #5282 from willww64/fix-configfile-relative-symlink
correctly handle configuration file saving when it is a relative symlink
2025-05-22 12:08:40 +00:00
9c8666c106 docs: deprecated: mark API < v1.24 as "removed"
Support for API versions lower than v1.24 was removed in v26.0.
The DOCKER_MIN_API_VERSION environment-variable is still present
in the docker daemon, but can currently only be used to raise the
minimum version.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-22 13:37:44 +02:00
21d0466ff1 docs: deprecated: mark fluentd-async-connect as "removed"
The daemon still has migration code in place, but no longer accepts
the option for new containers, so marking it as "removed";
49ec488036

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-22 13:28:18 +02:00
1d3b933ac3 Merge pull request #6098 from thaJeztah/deprecated_inspect_fields
docs: deprecated: set Container and ContainerConfig fields to "removed"
2025-05-22 11:54:10 +02:00
649d088ddf Merge pull request #6096 from thaJeztah/graphdriver_plugin_deprecation
docs: update deprecation status of graphdriver-plugins to "removed"
2025-05-22 11:49:53 +02:00
e135563c1f Merge pull request #6097 from vvoland/28.x
vendor: github.com/docker/docker v28.2.0-dev (f4ffeb8c38b3)
2025-05-22 09:48:20 +00:00
026ae7a11f docs: deprecated: set Container and ContainerConfig fields to "removed"
These fields have been removed in v26.0 (API v1.45 and up), and are always
omitted when using the containerd image store;
03cddc62f4

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-22 11:45:57 +02:00
681c705be6 vendor: github.com/docker/docker v28.2.0-dev (f4ffeb8c38b3)
full diff: b590eff717...f4ffeb8c38

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-05-22 11:38:51 +02:00
bdd994b79a docs: update deprecation status of graphdriver-plugins to "removed"
This functionality, was removed and the DOCKERD_DEPRECATED_GRAPHDRIVER_PLUGINS
no longer can be used in v28.0;
42ca9154e9

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-22 11:29:37 +02:00
1015d15621 fix bug with config file being a relative symlink
- use filepath.EvalSymlink instead of check with filepath.IsAbs
- allow for dangling symlinks
- extract path from error when NotExist error occurs

Co-authored-by: Paweł Gronowski <me@woland.xyz>
Co-authored-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Will Wang <willww64@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-22 11:11:07 +02:00
ae922ec177 Merge pull request #6092 from thaJeztah/hijack_oncefunc
cli/command/container: hijackedIOStreamer.setupInput: use sync.OnceFunc
2025-05-22 10:02:11 +02:00
881c68f690 Merge pull request #4966 from Benehiko/relative-mount-path
feat: relative parent paths on bind mount src
2025-05-22 08:38:06 +02:00
761285bfee feat: relative parent paths on bind mount src
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-05-22 08:15:32 +02:00
f23ec25a12 Merge pull request #6095 from thaJeztah/update_schema1_deprecation
docs: move deprecation status of legacy schema1 images to "removed"
2025-05-22 08:06:45 +02:00
565b0a2822 Merge pull request #6094 from thaJeztah/fix_isautomated_status
docs: fix deprecation status for IsAutomated
2025-05-22 07:58:53 +02:00
4be9afb801 Merge pull request #6086 from thaJeztah/golangci_tweaks
golangci-lint: enable more linters, and some minor linting fixes
2025-05-22 07:57:53 +02:00
f05025caf9 docs: move deprecation status of legacy schema1 images to "removed"
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-22 01:10:13 +02:00
32a8f4c420 docs: fix deprecation status for IsAutomated
Follow-up to 6e4315f599, where
I forgot to update the status column.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-22 00:34:13 +02:00
68ef98d801 Merge pull request #6093 from vvoland/vendor-docker
vendor: github.com/docker/docker v28.2.0-dev (b590eff717b3)
2025-05-21 17:51:04 +00:00
63f2984336 vendor: github.com/docker/docker v28.2.0-dev (b590eff717b3)
full diff: 8601b22f5d...b590eff717

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-05-21 19:45:33 +02:00
0ee20b8543 Merge pull request #5995 from vvoland/swarm-init-cacert
swarm/init: Fix `--external-ca` ignoring `cacert` option
2025-05-21 18:31:14 +02:00
c07cd8aaad Merge pull request #6091 from thaJeztah/remove_deprecated_isautomated
search: remove deprecated "IsAutomated" placeholder
2025-05-21 15:43:36 +00:00
bf2eea31b5 cli/command/container: hijackedIOStreamer.setupInput: use sync.OnceFunc
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-21 17:34:51 +02:00
6e4315f599 search: remove deprecated "IsAutomated" placeholder
IsAutomated was deprecated in 4fc3f0e6f6
(docker v25.0), and marked for removal in docker 26.0 (which we missed).
This removes it.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-21 17:13:59 +02:00
97e060e7b1 Merge pull request #6042 from thaJeztah/carry_5804_docker_ps_platform
docker ps: add "Platform" as formatting option
2025-05-21 12:28:57 +00:00
67c0be4b05 docker ps: allow formatting as JSON
With this patch:

    docker ps --format 'table {{.Names}}\t{{.Platform}}'
    NAMES                    PLATFORM
    optimistic_nightingale   linux/arm64
    mycontainer              linux/arm64/v8
    trusting_goldstine       linux/arm64

    docker ps --format '{{.Platform}}'
    linux/arm64
    linux/arm64/v8
    linux/arm64

    docker ps --format '{{json .Platform}}'
    {"architecture":"arm64","os":"linux"}
    {"architecture":"arm64","os":"linux","variant":"v8"}
    {"architecture":"arm64","os":"linux"}

    docker ps --format 'json'
    {"Command":"\"/bin/bash\"","CreatedAt":"2025-05-13 10:12:19 +0000 UTC","ID":"e8b3b2d604f1","Image":"docker-cli-dev","Labels":"desktop.docker.io/binds/0/Source=/Users/thajeztah/go/src/github.com/docker/cli,desktop.docker.io/binds/0/SourceKind=hostFile,desktop.docker.io/binds/0/Target=/go/src/github.com/docker/cli,desktop.docker.io/mounts/0/Source=/var/run/docker.sock,desktop.docker.io/mounts/0/SourceKind=dockerSocketProxied,desktop.docker.io/mounts/0/Target=/var/run/docker.sock,desktop.docker.io/ports.scheme=v2","LocalVolumes":"1","Mounts":"/host_mnt/User…,docker-cli-dev…,/run/host-serv…","Names":"optimistic_nightingale","Networks":"bridge","Platform":{"architecture":"arm64","os":"linux"},"Ports":"","RunningFor":"38 minutes ago","Size":"0B","State":"running","Status":"Up 38 minutes"}
    {"Command":"\"/docker-entrypoint.…\"","CreatedAt":"2025-05-13 09:58:01 +0000 UTC","ID":"c93b808dd54e","Image":"nginx:alpine","Labels":"desktop.docker.io/ports.scheme=v2,maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e","LocalVolumes":"0","Mounts":"","Names":"mycontainer","Networks":"bridge","Platform":{"architecture":"arm64","os":"linux","variant":"v8"},"Ports":"80/tcp","RunningFor":"53 minutes ago","Size":"0B","State":"running","Status":"Up 53 minutes"}
    {"Command":"\"/usr/bin/gotty --ti…\"","CreatedAt":"2025-05-13 07:31:18 +0000 UTC","ID":"cbb981b06e46","Image":"thajeztah/dockershell:latest","Labels":"desktop.docker.io/ports.scheme=v2,com.thajeztah.docker-shell=1","LocalVolumes":"0","Mounts":"","Names":"trusting_goldstine","Networks":"bridge","Platform":{"architecture":"arm64","os":"linux"},"Ports":"0.0.0.0:55000-\u003e8080/tcp","RunningFor":"3 hours ago","Size":"0B","State":"running","Status":"Up 3 hours"}

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-21 12:44:38 +02:00
7aa6c79c0a docker ps: add "Platform" as formatting option
docker ps --format 'table {{.ID}}\t{{.Image}}{{.Command}}\t{{.RunningFor}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}\t{{.Platform}}'
    CONTAINER ID   IMAGECOMMAND                CREATED          STATUS          PORTS     NAMES              PLATFORM
    e422855eac55   docker-cli-dev"/bin/bash"   12 minutes ago   Up 12 minutes             strange_jennings   linux/arm64

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-21 12:43:01 +02:00
af090512a6 Merge pull request #6084 from thaJeztah/bump_engine
vendor: github.com/docker/docker 8601b22f5db5 (v28.2-dev)
2025-05-20 10:57:13 +02:00
067587bf15 Merge pull request #6085 from thaJeztah/bump_cli_docs_tool
vendor: github.com/docker/cli-docs-tool v0.10.0
2025-05-20 09:30:29 +02:00
ed5d9757c9 vendor: github.com/docker/docker 8601b22f5db5 (v28.2-dev)
full diff: https://github.com/docker/docker/compare/v28.2.0-rc.1...8601b22f5db511354d643a7722d11d33aa7ae13f

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 22:47:14 +02:00
89f38282fd vendor: github.com/docker/docker v28.2.0-rc.1
no diff: same commit, but tagged

full diff: https://github.com/docker/docker/compare/7937f0846c13...v28.2.0-rc.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 22:40:55 +02:00
9d027dff40 Dockerfile: update golangci-lint to v2.1.5
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 20:16:23 +02:00
d1e9946ab8 golangci-lint: enable more linters
Enables the asasalint, exptostd, fatcontext, gocheckcompilerdirectives,
iface, makezero, and spancheck linters.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 20:14:13 +02:00
615ffee13b golangci-lint: enable nosprintfhostport linter
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 20:14:12 +02:00
c1313a92a0 golangci-lint: enable makezero linter
cli/command/container/formatter_stats_test.go:339:11: append to slice `stats` with non-zero initialized length (makezero)
            stats = append(stats, entry)
                    ^

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 20:14:12 +02:00
18e911c958 cli/command/container: TestContainerStatsContextWriteTrunc: use subtests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 20:14:12 +02:00
d65f0c9bbf golangci-lint: enable exhaustive linter
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 20:14:04 +02:00
b64d9b3b19 golangci-lint: replace nilerr for nilnesserr
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 20:07:15 +02:00
062ad57ce2 golangci-lint: enable mirror linter
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 20:07:05 +02:00
915b3fe992 vendor: github.com/docker/cli-docs-tool v0.10.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 17:44:21 +02:00
2ef9ab4494 golangci-lint: align comments
Format comments to be the same as in moby/moby for easier comparing.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 15:36:11 +02:00
d14b7e8d09 golangci-lint: remove exclusions for ST1020, ST1022
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-19 15:33:44 +02:00
6c2d023d87 swarm/init: Fix --external-ca ignoring cacert option
31d6292458 mistakenly changed the `ToSpec`
function to set all certs passed via `external-ca` to empty strings.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-04-10 11:41:13 +02:00
a0385bf042 swarm/init: Test init --external-ca with custom cert
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-04-09 16:56:26 +02:00
586 changed files with 17036 additions and 5496 deletions

View File

@ -121,7 +121,8 @@ jobs:
type=semver,pattern={{version}}
type=ref,event=branch
type=ref,event=pr
type=sha
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
-
name: Build and push image
uses: docker/bake-action@v6

View File

@ -63,7 +63,7 @@ jobs:
name: Update Go
uses: actions/setup-go@v5
with:
go-version: "1.24.3"
go-version: "1.24.5"
-
name: Initialize CodeQL
uses: github/codeql-action/init@v3

View File

@ -66,7 +66,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.24.3"
go-version: "1.24.5"
-
name: Test
run: |

View File

@ -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.3"
go: "1.24.5"
timeout: 5m
@ -26,39 +26,49 @@ formatters:
linters:
enable:
- asasalint # Detects "[]any" used as argument for variadic "func(...any)".
- bodyclose
- copyloopvar # Detects places where loop variables are copied.
- copyloopvar # Detects places where loop variables are copied.
- depguard
- dogsled
- dupword # Detects duplicate words.
- durationcheck
- dogsled # Detects assignments with too many blank identifiers.
- dupword # Detects duplicate words.
- durationcheck # Detect cases where two time.Duration values are being multiplied in possibly erroneous ways.
- errcheck
- errchkjson
- errchkjson # Detects unsupported types passed to json encoding functions and reports if checks for the returned error can be omitted.
- exhaustive # Detects missing options in enum switch statements.
- exptostd # Detects functions from golang.org/x/exp/ that can be replaced by std functions.
- fatcontext # Detects nested contexts in loops and function literals.
- forbidigo
- gocritic # Metalinter; detects bugs, performance, and styling issues.
- gocheckcompilerdirectives # Detects invalid go compiler directive comments (//go:).
- gocritic # Metalinter; detects bugs, performance, and styling issues.
- gocyclo
- gosec # Detects security problems.
- gosec # Detects security problems.
- govet
- iface # Detects incorrect use of interfaces. Currently only used for "identical" interfaces in the same package.
- importas # Enforces consistent import aliases.
- ineffassign
- importas # Enforces consistent import aliases.
- misspell # Detects commonly misspelled English words in comments.
- nakedret # Detects uses of naked returns.
- nilerr # Detects code that returns nil even if it checks that the error is not nil.
- nolintlint # Detects ill-formed or insufficient nolint directives.
- perfsprint # Detects fmt.Sprintf uses that can be replaced with a faster alternative.
- prealloc # Detects slice declarations that could potentially be pre-allocated.
- predeclared # Detects code that shadows one of Go's predeclared identifiers
- reassign
- revive # Metalinter; drop-in replacement for golint.
- makezero # Finds slice declarations with non-zero initial length.
- mirror # Detects wrong mirror patterns of bytes/strings usage.
- misspell # Detects commonly misspelled English words in comments.
- nakedret # Detects uses of naked returns.
- nilnesserr # Detects returning nil errors. It combines the features of nilness and nilerr,
- nosprintfhostport # Detects misuse of Sprintf to construct a host with port in a URL.
- nolintlint # Detects ill-formed or insufficient nolint directives.
- perfsprint # Detects fmt.Sprintf uses that can be replaced with a faster alternative.
- prealloc # Detects slice declarations that could potentially be pre-allocated.
- predeclared # Detects code that shadows one of Go's predeclared identifiers
- reassign # Detects reassigning a top-level variable in another package.
- revive # Metalinter; drop-in replacement for golint.
- spancheck # Detects mistakes with OpenTelemetry/Census spans.
- staticcheck
- thelper # Detects test helpers without t.Helper().
- tparallel # Detects inappropriate usage of t.Parallel().
- unconvert # Detects unnecessary type conversions.
- thelper # Detects test helpers without t.Helper().
- tparallel # Detects inappropriate usage of t.Parallel().
- unconvert # Detects unnecessary type conversions.
- unparam
- unused
- usestdlibvars
- usetesting # Reports uses of functions with replacement inside the testing package.
- wastedassign
- usestdlibvars # Detects the possibility to use variables/constants from the Go standard library.
- usetesting # Reports uses of functions with replacement inside the testing package.
- wastedassign # Detects wasted assignment statements.
disable:
- errcheck
@ -132,9 +142,7 @@ linters:
staticcheck:
checks:
- all
- -QF1008 # Omit embedded fields from selector expression
- -ST1020 # The documentation of an exported function should start with the functions name
- -ST1022 # The documentation of an exported variable or constant should start with variables name
- -QF1008 # Omit embedded fields from selector expression; https://staticcheck.dev/docs/checks/#QF1008
revive:
rules:
@ -159,8 +167,8 @@ linters:
# (unlike the "include" option), the "exclude" option does not take exclusion
# ID's.
#
# These exclusion patterns are copied from the default excluses at:
# https://github.com/golangci/golangci-lint/blob/v1.44.0/pkg/config/issues.go#L10-L104
# These exclusion patterns are copied from the default excludes at:
# https://github.com/golangci/golangci-lint/blob/v1.61.0/pkg/config/issues.go#L11-L104
#
# The default list of exclusions can be found at:
# https://golangci-lint.run/usage/false-positives/#default-exclusions
@ -207,6 +215,12 @@ linters:
linters:
- govet
# Ignore for cli/command/formatter/tabwriter, which is forked from go stdlib, so we want to align with it.
- text: '^(ST1020|ST1022): comment on exported'
path: "cli/command/formatter/tabwriter"
linters:
- staticcheck
# Log a warning if an exclusion rule is unused.
# Default: false
warn-unused: true

View File

@ -4,7 +4,7 @@ ARG BASE_VARIANT=alpine
ARG ALPINE_VERSION=3.21
ARG BASE_DEBIAN_DISTRO=bookworm
ARG GO_VERSION=1.24.3
ARG GO_VERSION=1.24.5
ARG XX_VERSION=1.6.1
ARG GOVERSIONINFO_VERSION=v1.4.1
ARG GOTESTSUM_VERSION=v1.12.0
@ -12,8 +12,8 @@ ARG GOTESTSUM_VERSION=v1.12.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
# on Docker Hub.
ARG BUILDX_VERSION=0.23.0
ARG COMPOSE_VERSION=v2.35.1
ARG BUILDX_VERSION=0.24.0
ARG COMPOSE_VERSION=v2.36.2
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx

View File

@ -1 +1 @@
28.2.0-dev
28.3.0-dev

View File

@ -81,7 +81,7 @@ func addPluginCandidatesFromDir(res map[string][]string, d string) {
return
}
for _, dentry := range dentries {
switch dentry.Type() & os.ModeType {
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
default:

View File

@ -28,7 +28,7 @@ func NewContainerCommand(dockerCli command.Cli) *cobra.Command {
NewPortCommand(dockerCli),
NewRenameCommand(dockerCli),
NewRestartCommand(dockerCli),
NewRmCommand(dockerCli),
newRemoveCommand(dockerCli),
NewRunCommand(dockerCli),
NewStartCommand(dockerCli),
NewStatsCommand(dockerCli),

View File

@ -248,15 +248,14 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
// 1. Mount the actual docker socket.
// 2. A synthezised ~/.docker/config.json with resolved tokens.
socket := dockerCli.DockerEndpoint().Host
if !strings.HasPrefix(socket, "unix://") {
return "", fmt.Errorf("flag --use-api-socket can only be used with unix sockets: docker endpoint %s incompatible", socket)
if dockerCli.ServerInfo().OSType == "windows" {
return "", errors.New("flag --use-api-socket can't be used with a Windows Docker Engine")
}
socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
// hard-code engine socket path until https://github.com/moby/moby/pull/43459 gives us a discovery mechanism
containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
Type: mount.TypeBind,
Source: socket,
Source: "/var/run/docker.sock",
Target: "/var/run/docker.sock",
BindOptions: &mount.BindOptions{},
})

View File

@ -99,7 +99,7 @@ func RunExec(ctx context.Context, dockerCLI command.Cli, containerIDorName strin
if _, err := apiClient.ContainerInspect(ctx, containerIDorName); err != nil {
return err
}
if !execOptions.Detach {
if !options.Detach {
if err := dockerCLI.In().CheckTty(execOptions.AttachStdin, execOptions.Tty); err != nil {
return err
}
@ -117,9 +117,9 @@ func RunExec(ctx context.Context, dockerCLI command.Cli, containerIDorName strin
return errors.New("exec ID empty")
}
if execOptions.Detach {
if options.Detach {
return apiClient.ContainerExecStart(ctx, execID, container.ExecStartOptions{
Detach: execOptions.Detach,
Detach: options.Detach,
Tty: execOptions.Tty,
ConsoleSize: execOptions.ConsoleSize,
})
@ -223,7 +223,6 @@ func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*contai
Privileged: execOpts.Privileged,
Tty: execOpts.TTY,
Cmd: execOpts.Command,
Detach: execOpts.Detach,
WorkingDir: execOpts.Workdir,
}

View File

@ -5,6 +5,7 @@ import (
"errors"
"io"
"os"
"strconv"
"testing"
"github.com/docker/cli/cli"
@ -75,8 +76,7 @@ TWO=2
{
options: withDefaultOpts(ExecOptions{Detach: true}),
expected: container.ExecOptions{
Detach: true,
Cmd: []string{"command"},
Cmd: []string{"command"},
},
},
{
@ -86,9 +86,8 @@ TWO=2
Detach: true,
}),
expected: container.ExecOptions{
Detach: true,
Tty: true,
Cmd: []string{"command"},
Tty: true,
Cmd: []string{"command"},
},
},
{
@ -97,7 +96,6 @@ TWO=2
expected: container.ExecOptions{
Cmd: []string{"command"},
DetachKeys: "de",
Detach: true,
},
},
{
@ -109,7 +107,6 @@ TWO=2
expected: container.ExecOptions{
Cmd: []string{"command"},
DetachKeys: "ab",
Detach: true,
},
},
{
@ -141,10 +138,12 @@ TWO=2
},
}
for _, testcase := range testcases {
execConfig, err := parseExec(testcase.options, &testcase.configFile)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(testcase.expected, *execConfig))
for i, testcase := range testcases {
t.Run("test "+strconv.Itoa(i+1), func(t *testing.T) {
execConfig, err := parseExec(testcase.options, &testcase.configFile)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(testcase.expected, *execConfig))
})
}
}

View File

@ -148,7 +148,7 @@ container2 -- --
`,
},
}
stats := []StatsEntry{
entries := []StatsEntry{
{
Container: "container1",
CPUPercentage: 20,
@ -181,7 +181,7 @@ container2 -- --
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out
err := statsFormatWrite(tc.context, stats, "windows", false)
err := statsFormatWrite(tc.context, entries, "windows", false)
if err != nil {
assert.Error(t, err, tc.expected)
} else {
@ -273,45 +273,46 @@ func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
}
func TestContainerStatsContextWriteTrunc(t *testing.T) {
var out bytes.Buffer
contexts := []struct {
tests := []struct {
doc string
context formatter.Context
trunc bool
expected string
}{
{
formatter.Context{
doc: "non-truncated",
context: formatter.Context{
Format: "{{.ID}}",
Output: &out,
},
false,
"b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc\n",
expected: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc\n",
},
{
formatter.Context{
doc: "truncated",
context: formatter.Context{
Format: "{{.ID}}",
Output: &out,
},
true,
"b95a83497c91\n",
trunc: true,
expected: "b95a83497c91\n",
},
}
for _, context := range contexts {
statsFormatWrite(context.context, []StatsEntry{{ID: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc"}}, "linux", context.trunc)
assert.Check(t, is.Equal(context.expected, out.String()))
// Clean buffer
out.Reset()
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out
err := statsFormatWrite(tc.context, []StatsEntry{{ID: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc"}}, "linux", tc.trunc)
assert.NilError(t, err)
assert.Check(t, is.Equal(tc.expected, out.String()))
})
}
}
func BenchmarkStatsFormat(b *testing.B) {
b.ReportAllocs()
stats := genStats()
entries := genStats()
for i := 0; i < b.N; i++ {
for _, s := range stats {
for _, s := range entries {
_ = s.CPUPerc()
_ = s.MemUsage()
_ = s.MemPerc()
@ -334,9 +335,9 @@ func genStats() []statsContext {
NetworkTx: 987.654321,
PidsCurrent: 123456789,
}}
stats := make([]statsContext, 100)
for i := 0; i < 100; i++ {
stats = append(stats, entry)
entries := make([]statsContext, 0, 100)
for range 100 {
entries = append(entries, entry)
}
return stats
return entries
}

View File

@ -84,12 +84,9 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
// Use sync.Once so we may call restore multiple times but ensure we
// only restore the terminal once.
var restoreOnce sync.Once
restore = func() {
restoreOnce.Do(func() {
_ = restoreTerminal(h.streams, h.inputStream)
})
}
restore = sync.OnceFunc(func() {
_ = restoreTerminal(h.streams, h.inputStream)
})
// Wrap the input to detect detach escape sequence.
// Use default escape keys if an invalid sequence is given.

View File

@ -18,7 +18,6 @@ import (
"github.com/docker/docker/api/types/container"
mounttypes "github.com/docker/docker/api/types/mount"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/spf13/pflag"
@ -375,7 +374,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
if parsed.Type == string(mounttypes.TypeBind) {
if hostPart, targetPath, ok := strings.Cut(bind, ":"); ok {
if strings.HasPrefix(hostPart, "."+string(filepath.Separator)) || hostPart == "." {
if !filepath.IsAbs(hostPart) && strings.HasPrefix(hostPart, ".") {
if absHostPart, err := filepath.Abs(hostPart); err == nil {
hostPart = absHostPart
}
@ -400,17 +399,14 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
tmpfs[k] = v
}
var (
runCmd strslice.StrSlice
entrypoint strslice.StrSlice
)
var runCmd, entrypoint []string
if len(copts.Args) > 0 {
runCmd = copts.Args
}
if copts.entrypoint != "" {
entrypoint = strslice.StrSlice{copts.entrypoint}
entrypoint = []string{copts.entrypoint}
} else if flags.Changed("entrypoint") {
// if `--entrypoint=` is parsed then Entrypoint is reset
entrypoint = []string{""}
@ -551,9 +547,9 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
if haveHealthSettings {
return nil, errors.Errorf("--no-healthcheck conflicts with --health-* options")
}
healthConfig = &container.HealthConfig{Test: strslice.StrSlice{"NONE"}}
healthConfig = &container.HealthConfig{Test: []string{"NONE"}}
} else if haveHealthSettings {
var probe strslice.StrSlice
var probe []string
if copts.healthCmd != "" {
probe = []string{"CMD-SHELL", copts.healthCmd}
}
@ -675,8 +671,8 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
UTSMode: utsMode,
UsernsMode: usernsMode,
CgroupnsMode: cgroupnsMode,
CapAdd: strslice.StrSlice(copts.capAdd.GetSlice()),
CapDrop: strslice.StrSlice(copts.capDrop.GetSlice()),
CapAdd: copts.capAdd.GetSlice(),
CapDrop: copts.capDrop.GetSlice(),
GroupAdd: copts.groupAdd.GetSlice(),
RestartPolicy: restartPolicy,
SecurityOpt: securityOpts,

View File

@ -27,10 +27,9 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
var opts rmOptions
cmd := &cobra.Command{
Use: "rm [OPTIONS] CONTAINER [CONTAINER...]",
Aliases: []string{"remove"},
Short: "Remove one or more containers",
Args: cli.RequiresMinArgs(1),
Use: "rm [OPTIONS] CONTAINER [CONTAINER...]",
Short: "Remove one or more containers",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args
return runRm(cmd.Context(), dockerCli, &opts)
@ -50,6 +49,15 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
// newRemoveCommand adds subcommands for "docker container"; unlike the
// 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.Aliases = []string{"rm", "remove"}
return &cmd
}
func runRm(ctx context.Context, dockerCLI command.Cli, opts *rmOptions) error {
apiClient := dockerCLI.Client()
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, ctrID string) error {

View File

@ -75,8 +75,8 @@ func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, co
eventProcessor := func(e events.Message) bool {
stopProcessing := false
switch e.Status {
case "die":
switch e.Action { //nolint:exhaustive // TODO(thaJeztah): make exhaustive
case events.ActionDie:
if v, ok := e.Actor.Attributes["exitCode"]; ok {
code, cerr := strconv.Atoi(v)
if cerr != nil {
@ -98,10 +98,10 @@ func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, co
}
}()
}
case "detach":
case events.ActionDetach:
exitCode = 0
stopProcessing = true
case "destroy":
case events.ActionDestroy:
stopProcessing = true
}
return stopProcessing

View File

@ -11,10 +11,12 @@ import (
"strings"
"time"
"github.com/containerd/platforms"
"github.com/distribution/reference"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
const (
@ -26,8 +28,18 @@ const (
mountsHeader = "MOUNTS"
localVolumes = "LOCAL VOLUMES"
networksHeader = "NETWORKS"
platformHeader = "PLATFORM"
)
// Platform wraps a [ocispec.Platform] to implement the stringer interface.
type Platform struct {
ocispec.Platform
}
func (p Platform) String() string {
return platforms.FormatAll(p.Platform)
}
// NewContainerFormat returns a Format for rendering using a Context
func NewContainerFormat(source string, quiet bool, size bool) Format {
switch source {
@ -109,6 +121,7 @@ func NewContainerContext() *ContainerContext {
"Mounts": mountsHeader,
"LocalVolumes": localVolumes,
"Networks": networksHeader,
"Platform": platformHeader,
}
return &containerCtx
}
@ -208,6 +221,16 @@ func (c *ContainerContext) RunningFor() string {
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
}
// Platform returns a human-readable representation of the container's
// platform if it is available.
func (c *ContainerContext) Platform() *Platform {
p := c.c.ImageManifestDescriptor
if p == nil || p.Platform == nil {
return nil
}
return &Platform{*p.Platform}
}
// Ports returns a comma-separated string representing open ports of the container
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
// it's used by command 'docker ps'

View File

@ -14,6 +14,7 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/stringid"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
@ -425,13 +426,36 @@ func TestContainerContextWriteWithNoContainers(t *testing.T) {
func TestContainerContextWriteJSON(t *testing.T) {
unix := time.Now().Add(-65 * time.Second).Unix()
containers := []container.Summary{
{ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu", Created: unix, State: container.StateRunning},
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu", Created: unix, State: container.StateRunning},
{
ID: "containerID1",
Names: []string{"/foobar_baz"},
Image: "ubuntu",
Created: unix,
State: container.StateRunning,
},
{
ID: "containerID2",
Names: []string{"/foobar_bar"},
Image: "ubuntu",
Created: unix,
State: container.StateRunning,
ImageManifestDescriptor: &ocispec.Descriptor{Platform: &ocispec.Platform{Architecture: "amd64", OS: "linux"}},
},
{
ID: "containerID3",
Names: []string{"/foobar_bar"},
Image: "ubuntu",
Created: unix,
State: container.StateRunning,
ImageManifestDescriptor: &ocispec.Descriptor{Platform: &ocispec.Platform{}},
},
}
expectedCreated := time.Unix(unix, 0).String()
expectedJSONs := []map[string]any{
{
"Command": "\"\"",
"Command": `""`,
"CreatedAt": expectedCreated,
"ID": "containerID1",
"Image": "ubuntu",
@ -440,6 +464,7 @@ func TestContainerContextWriteJSON(t *testing.T) {
"Mounts": "",
"Names": "foobar_baz",
"Networks": "",
"Platform": nil,
"Ports": "",
"RunningFor": "About a minute ago",
"Size": "0B",
@ -447,7 +472,7 @@ func TestContainerContextWriteJSON(t *testing.T) {
"Status": "",
},
{
"Command": "\"\"",
"Command": `""`,
"CreatedAt": expectedCreated,
"ID": "containerID2",
"Image": "ubuntu",
@ -456,6 +481,24 @@ func TestContainerContextWriteJSON(t *testing.T) {
"Mounts": "",
"Names": "foobar_bar",
"Networks": "",
"Platform": map[string]any{"architecture": "amd64", "os": "linux"},
"Ports": "",
"RunningFor": "About a minute ago",
"Size": "0B",
"State": "running",
"Status": "",
},
{
"Command": `""`,
"CreatedAt": expectedCreated,
"ID": "containerID3",
"Image": "ubuntu",
"Labels": "",
"LocalVolumes": "0",
"Mounts": "",
"Names": "foobar_bar",
"Networks": "",
"Platform": map[string]any{"architecture": "", "os": ""},
"Ports": "",
"RunningFor": "About a minute ago",
"Size": "0B",

View File

@ -20,6 +20,8 @@ func charWidth(r rune) int {
switch width.LookupRune(r).Kind() {
case width.EastAsianWide, width.EastAsianFullwidth:
return 2
case width.Neutral, width.EastAsianAmbiguous, width.EastAsianNarrow, width.EastAsianHalfwidth:
return 1
default:
return 1
}

View File

@ -3,7 +3,6 @@ package idresolver
import (
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
)
@ -21,7 +20,7 @@ func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, nodeID string) (swa
return swarm.Node{}, []byte{}, nil
}
func (cli *fakeClient) ServiceInspectWithRaw(_ context.Context, serviceID string, _ types.ServiceInspectOptions) (swarm.Service, []byte, error) {
func (cli *fakeClient) ServiceInspectWithRaw(_ context.Context, serviceID string, _ swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
if cli.serviceInspectFunc != nil {
return cli.serviceInspectFunc(serviceID)
}

View File

@ -6,7 +6,6 @@ package idresolver
import (
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/pkg/errors"
@ -44,7 +43,7 @@ func (r *IDResolver) get(ctx context.Context, t any, id string) (string, error)
}
return id, nil
case swarm.Service:
service, _, err := r.client.ServiceInspectWithRaw(ctx, id, types.ServiceInspectOptions{})
service, _, err := r.client.ServiceInspectWithRaw(ctx, id, swarm.ServiceInspectOptions{})
if err != nil {
// TODO(thaJeztah): should error-handling be more specific, or is it ok to ignore any error?
return id, nil //nolint:nilerr // ignore nil-error being returned, as this is a best-effort.

View File

@ -74,6 +74,8 @@ 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())
@ -113,7 +115,10 @@ To push the complete multi-platform image, remove the --platform flag.
if err != nil {
return err
}
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push")
var requestPrivilege registrytypes.RequestAuthConfig
if dockerCli.In().IsTerminal() {
requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push")
}
options := image.PushOptions{
All: opts.all,
RegistryAuth: encodedAuth,

View File

@ -6,6 +6,7 @@ import (
"fmt"
cerrdefs "github.com/containerd/errdefs"
"github.com/containerd/platforms"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
@ -14,22 +15,23 @@ import (
)
type removeOptions struct {
force bool
noPrune bool
force bool
noPrune bool
platforms []string
}
// NewRemoveCommand creates a new `docker remove` command
func NewRemoveCommand(dockerCli command.Cli) *cobra.Command {
var opts removeOptions
func NewRemoveCommand(dockerCLI command.Cli) *cobra.Command {
var options removeOptions
cmd := &cobra.Command{
Use: "rmi [OPTIONS] IMAGE [IMAGE...]",
Short: "Remove one or more images",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runRemove(cmd.Context(), dockerCli, opts, args)
return runRemove(cmd.Context(), dockerCLI, options, args)
},
ValidArgsFunction: completion.ImageNames(dockerCli, -1),
ValidArgsFunction: completion.ImageNames(dockerCLI, -1),
Annotations: map[string]string{
"aliases": "docker image rm, docker image remove, docker rmi",
},
@ -37,9 +39,14 @@ func NewRemoveCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Force removal of the image")
flags.BoolVar(&opts.noPrune, "no-prune", false, "Do not delete untagged parents")
flags.BoolVarP(&options.force, "force", "f", false, "Force removal of the image")
flags.BoolVar(&options.noPrune, "no-prune", false, "Do not delete untagged parents")
// TODO(thaJeztah): create a "platforms" option for this (including validation / parsing).
flags.StringSliceVar(&options.platforms, "platform", nil, `Remove only the given platform variant. Formatted as "os[/arch[/variant]]" (e.g., "linux/amd64")`)
_ = flags.SetAnnotation("platform", "version", []string{"1.50"})
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
return cmd
}
@ -58,6 +65,14 @@ func runRemove(ctx context.Context, dockerCLI command.Cli, opts removeOptions, i
PruneChildren: !opts.noPrune,
}
for _, v := range opts.platforms {
p, err := platforms.Parse(v)
if err != nil {
return err
}
options.Platforms = append(options.Platforms, p)
}
// TODO(thaJeztah): this logic can likely be simplified: do we want to print "not found" errors at all when using "force"?
fatalErr := false
var errs []error

View File

@ -170,10 +170,16 @@ func getPossibleChips(view treeView) (chips []imageChip) {
var possible []imageChip
for _, img := range view.images {
details := []imageDetails{img.Details}
for _, c := range img.Children {
details = append(details, c.Details)
}
for _, d := range details {
for idx := len(remaining) - 1; idx >= 0; idx-- {
chip := remaining[idx]
if chip.check(&c.Details) {
if chip.check(&d) {
possible = append(possible, chip)
remaining = append(remaining[:idx], remaining[idx+1:]...)
}

View File

@ -149,7 +149,10 @@ func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth tru
if err != nil {
return err
}
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "pull")
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,

View File

@ -3,7 +3,6 @@ package node
import (
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/client"
@ -17,8 +16,8 @@ type fakeClient struct {
nodeRemoveFunc func() error
nodeUpdateFunc func(nodeID string, version swarm.Version, node swarm.NodeSpec) error
taskInspectFunc func(taskID string) (swarm.Task, []byte, error)
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
serviceInspectFunc func(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error)
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
serviceInspectFunc func(ctx context.Context, serviceID string, opts swarm.ServiceInspectOptions) (swarm.Service, []byte, error)
}
func (cli *fakeClient) NodeInspectWithRaw(context.Context, string) (swarm.Node, []byte, error) {
@ -28,14 +27,14 @@ func (cli *fakeClient) NodeInspectWithRaw(context.Context, string) (swarm.Node,
return swarm.Node{}, []byte{}, nil
}
func (cli *fakeClient) NodeList(context.Context, types.NodeListOptions) ([]swarm.Node, error) {
func (cli *fakeClient) NodeList(context.Context, swarm.NodeListOptions) ([]swarm.Node, error) {
if cli.nodeListFunc != nil {
return cli.nodeListFunc()
}
return []swarm.Node{}, nil
}
func (cli *fakeClient) NodeRemove(context.Context, string, types.NodeRemoveOptions) error {
func (cli *fakeClient) NodeRemove(context.Context, string, swarm.NodeRemoveOptions) error {
if cli.nodeRemoveFunc != nil {
return cli.nodeRemoveFunc()
}
@ -63,14 +62,14 @@ func (cli *fakeClient) TaskInspectWithRaw(_ context.Context, taskID string) (swa
return swarm.Task{}, []byte{}, nil
}
func (cli *fakeClient) TaskList(_ context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
func (cli *fakeClient) TaskList(_ context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
if cli.taskListFunc != nil {
return cli.taskListFunc(options)
}
return []swarm.Task{}, nil
}
func (cli *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) {
func (cli *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
if cli.serviceInspectFunc != nil {
return cli.serviceInspectFunc(ctx, serviceID, opts)
}

View File

@ -6,7 +6,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/spf13/cobra"
)
@ -48,7 +48,7 @@ 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.
_, err = apiClient.NodeList(ctx, types.NodeListOptions{})
_, err = apiClient.NodeList(ctx, swarm.NodeListOptions{})
if err != nil {
return "", err
}

View File

@ -4,7 +4,7 @@ import (
"os"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/spf13/cobra"
)
@ -17,7 +17,7 @@ func completeNodeNames(dockerCLI completion.APIClientProvider) cobra.CompletionF
// https://github.com/docker/cli/blob/f9ced58158d5e0b358052432244b483774a1983d/contrib/completion/bash/docker#L41-L43
showIDs := os.Getenv("DOCKER_COMPLETION_SHOW_NODE_IDS") == "yes"
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCLI.Client().NodeList(cmd.Context(), types.NodeListOptions{})
list, err := dockerCLI.Client().NodeList(cmd.Context(), swarm.NodeListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}

View File

@ -10,7 +10,7 @@ import (
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
"github.com/fvbommel/sortorder"
"github.com/spf13/cobra"
@ -55,7 +55,7 @@ func runList(ctx context.Context, dockerCli command.Cli, options listOptions) er
nodes, err := client.NodeList(
ctx,
types.NodeListOptions{Filters: options.filter.Value()})
swarm.NodeListOptions{Filters: options.filter.Value()})
if err != nil {
return err
}

View File

@ -10,7 +10,6 @@ import (
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/command/task"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -84,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, types.TaskListOptions{Filters: filter})
nodeTasks, err := client.TaskList(ctx, swarm.TaskListOptions{Filters: filter})
if err != nil {
errs = append(errs, err.Error())
continue

View File

@ -10,7 +10,6 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
"gotest.tools/v3/assert"
@ -23,7 +22,7 @@ func TestNodePsErrors(t *testing.T) {
flags map[string]string
infoFunc func() (system.Info, error)
nodeInspectFunc func() (swarm.Node, []byte, error)
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
taskInspectFunc func(taskID string) (swarm.Task, []byte, error)
expectedError string
}{
@ -42,7 +41,7 @@ func TestNodePsErrors(t *testing.T) {
},
{
args: []string{"nodeID"},
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{}, errors.New("error returning the task list")
},
expectedError: "error returning the task list",
@ -73,9 +72,9 @@ func TestNodePs(t *testing.T) {
flags map[string]string
infoFunc func() (system.Info, error)
nodeInspectFunc func() (swarm.Node, []byte, error)
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
taskInspectFunc func(taskID string) (swarm.Task, []byte, error)
serviceInspectFunc func(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error)
serviceInspectFunc func(ctx context.Context, serviceID string, opts swarm.ServiceInspectOptions) (swarm.Service, []byte, error)
}{
{
name: "simple",
@ -83,7 +82,7 @@ func TestNodePs(t *testing.T) {
nodeInspectFunc: func() (swarm.Node, []byte, error) {
return *builders.Node(), []byte{}, nil
},
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{
*builders.Task(builders.WithStatus(builders.Timestamp(time.Now().Add(-2*time.Hour)), builders.PortStatus([]swarm.PortConfig{
{
@ -94,7 +93,7 @@ func TestNodePs(t *testing.T) {
}))),
}, nil
},
serviceInspectFunc: func(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) {
serviceInspectFunc: func(ctx context.Context, serviceID string, opts swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
return swarm.Service{
ID: serviceID,
Spec: swarm.ServiceSpec{
@ -111,7 +110,7 @@ func TestNodePs(t *testing.T) {
nodeInspectFunc: func() (swarm.Node, []byte, error) {
return *builders.Node(), []byte{}, nil
},
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{
*builders.Task(builders.TaskID("taskID1"), builders.TaskServiceID("failure"),
builders.WithStatus(builders.Timestamp(time.Now().Add(-2*time.Hour)), builders.StatusErr("a task error"))),
@ -121,7 +120,7 @@ func TestNodePs(t *testing.T) {
builders.WithStatus(builders.Timestamp(time.Now().Add(-4*time.Hour)), builders.StatusErr("a task error"))),
}, nil
},
serviceInspectFunc: func(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) {
serviceInspectFunc: func(ctx context.Context, serviceID string, opts swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
return swarm.Service{
ID: serviceID,
Spec: swarm.ServiceSpec{

View File

@ -7,7 +7,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/spf13/cobra"
)
@ -38,7 +38,7 @@ func runRemove(ctx context.Context, dockerCLI command.Cli, nodeIDs []string, opt
var errs []error
for _, id := range nodeIDs {
if err := apiClient.NodeRemove(ctx, id, types.NodeRemoveOptions{Force: opts.force}); err != nil {
if err := apiClient.NodeRemove(ctx, id, swarm.NodeRemoveOptions{Force: opts.force}); err != nil {
errs = append(errs, err)
continue
}

View File

@ -90,13 +90,18 @@ 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: command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, cmdName),
PrivilegeFunc: requestPrivilege,
Args: opts.args,
}
return options, nil

View File

@ -35,7 +35,7 @@ const (
const authConfigKey = "https://index.docker.io/v1/"
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
// for the given command.
// for the given command to prompt the user for username and password.
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
configKey := getAuthConfigKey(index.Name)
isDefaultRegistry := configKey == authConfigKey || index.Official
@ -43,7 +43,7 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf
_, _ = fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, configKey, isDefaultRegistry)
if err != nil {
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", authConfigKey, err)
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", configKey, err)
}
select {
@ -52,7 +52,7 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf
default:
}
authConfig, err = PromptUserForCredentials(ctx, cli, "", "", authConfig.Username, authConfigKey)
authConfig, err = PromptUserForCredentials(ctx, cli, "", "", authConfig.Username, configKey)
if err != nil {
return "", err
}

View File

@ -42,7 +42,6 @@ func SearchWrite(ctx formatter.Context, results []registrytypes.SearchResult) er
"Description": formatter.DescriptionHeader,
"StarCount": starsHeader,
"IsOfficial": officialHeader,
"IsAutomated": automatedHeader,
}
return ctx.Write(&searchCtx, render)
}
@ -92,10 +91,3 @@ func (c *searchContext) formatBool(value bool) string {
func (c *searchContext) IsOfficial() string {
return c.formatBool(c.s.IsOfficial)
}
// IsAutomated formats the IsAutomated field for printing.
//
// Deprecated: the "is_automated" field is deprecated and will always be "false" in the future.
func (c *searchContext) IsAutomated() string {
return c.formatBool(c.s.IsAutomated) //nolint:nolintlint,staticcheck // ignore SA1019 (IsAutomated is deprecated).
}

View File

@ -45,19 +45,6 @@ func TestSearchContext(t *testing.T) {
},
call: ctx.IsOfficial,
},
{
searchCtx: searchContext{
s: registrytypes.SearchResult{IsAutomated: true}, //nolint:nolintlint,staticcheck // ignore SA1019 (IsAutomated is deprecated).
},
expValue: "[OK]",
call: ctx.IsAutomated, //nolint:nolintlint,staticcheck // ignore SA1019 (IsAutomated is deprecated).
},
{
searchCtx: searchContext{
s: registrytypes.SearchResult{},
},
call: ctx.IsAutomated, //nolint:nolintlint,staticcheck // ignore SA1019 (IsAutomated is deprecated).
},
}
for _, c := range cases {
@ -157,8 +144,8 @@ func TestSearchContextWrite(t *testing.T) {
{
doc: "JSON format",
format: "{{json .}}",
expected: `{"Description":"Official build","IsAutomated":"false","IsOfficial":"true","Name":"result1","StarCount":"5000"}
{"Description":"Not official","IsAutomated":"true","IsOfficial":"false","Name":"result2","StarCount":"5"}
expected: `{"Description":"Official build","IsOfficial":"true","Name":"result1","StarCount":"5000"}
{"Description":"Not official","IsOfficial":"false","Name":"result2","StarCount":"5"}
`,
},
{
@ -199,7 +186,7 @@ result2 5
results := []registrytypes.SearchResult{
{Name: "result1", Description: "Official build", StarCount: 5000, IsOfficial: true},
{Name: "result2", Description: "Not official", StarCount: 5, IsAutomated: true},
{Name: "result2", Description: "Not official", StarCount: 5},
}
for _, tc := range cases {

View File

@ -110,6 +110,9 @@ func runLogin(ctx context.Context, dockerCLI command.Cli, opts loginOptions) err
if err := verifyLoginOptions(dockerCLI, &opts); err != nil {
return err
}
maybePrintEnvAuthWarning(dockerCLI)
var (
serverAddress string
msg string

View File

@ -36,6 +36,8 @@ func NewLogoutCommand(dockerCli command.Cli) *cobra.Command {
}
func runLogout(ctx context.Context, dockerCLI command.Cli, serverAddress string) error {
maybePrintEnvAuthWarning(dockerCLI)
var isDefaultRegistry bool
if serverAddress == "" {

View File

@ -63,7 +63,10 @@ func runSearch(ctx context.Context, dockerCli command.Cli, options searchOptions
return err
}
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, indexInfo, "search")
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,

View File

@ -0,0 +1,18 @@
package registry
import (
"os"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/internal/tui"
)
// maybePrintEnvAuthWarning if the `DOCKER_AUTH_CONFIG` environment variable is
// set this function will output a warning to stdErr
func maybePrintEnvAuthWarning(out command.Streams) {
if os.Getenv(configfile.DockerEnvConfigKey) != "" {
tui.NewOutput(out.Err()).
PrintWarning("%[1]s is set and takes precedence.\nUnset %[1]s to restore the CLI auth behaviour.\n", configfile.DockerEnvConfigKey)
}
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
@ -13,30 +12,30 @@ import (
type fakeClient struct {
client.Client
serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error)
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
serviceListFunc func(context.Context, types.ServiceListOptions) ([]swarm.Service, error)
taskListFunc func(context.Context, types.TaskListOptions) ([]swarm.Task, error)
serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error)
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
serviceListFunc func(context.Context, swarm.ServiceListOptions) ([]swarm.Service, error)
taskListFunc func(context.Context, swarm.TaskListOptions) ([]swarm.Task, error)
infoFunc func(ctx context.Context) (system.Info, error)
networkInspectFunc func(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, error)
nodeListFunc func(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
nodeListFunc func(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error)
}
func (f *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
func (f *fakeClient) NodeList(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
if f.nodeListFunc != nil {
return f.nodeListFunc(ctx, options)
}
return nil, nil
}
func (f *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
func (f *fakeClient) TaskList(ctx context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
if f.taskListFunc != nil {
return f.taskListFunc(ctx, options)
}
return nil, nil
}
func (f *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
func (f *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
if f.serviceInspectWithRawFunc != nil {
return f.serviceInspectWithRawFunc(ctx, serviceID, options)
}
@ -44,7 +43,7 @@ func (f *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string
return *builders.Service(builders.ServiceID(serviceID)), []byte{}, nil
}
func (f *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
func (f *fakeClient) ServiceList(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
if f.serviceListFunc != nil {
return f.serviceListFunc(ctx, options)
}
@ -52,7 +51,7 @@ func (f *fakeClient) ServiceList(ctx context.Context, options types.ServiceListO
return nil, nil
}
func (f *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
func (f *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
if f.serviceUpdateFunc != nil {
return f.serviceUpdateFunc(ctx, serviceID, version, service, options)
}

View File

@ -4,7 +4,7 @@ import (
"os"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/spf13/cobra"
)
@ -15,7 +15,7 @@ func completeServiceNames(dockerCLI completion.APIClientProvider) cobra.Completi
// https://github.com/docker/cli/blob/f9ced58158d5e0b358052432244b483774a1983d/contrib/completion/bash/docker#L41-L43
showIDs := os.Getenv("DOCKER_COMPLETION_SHOW_SERVICE_IDS") == "yes"
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCLI.Client().ServiceList(cmd.Context(), types.ServiceListOptions{})
list, err := dockerCLI.Client().ServiceList(cmd.Context(), swarm.ServiceListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}

View File

@ -8,7 +8,6 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
cliopts "github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
@ -102,7 +101,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
func runCreate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, opts *serviceOptions) error {
apiClient := dockerCLI.Client()
createOpts := types.ServiceCreateOptions{}
createOpts := swarm.ServiceCreateOptions{}
service, err := opts.ToService(ctx, apiClient, flags)
if err != nil {

View File

@ -13,8 +13,8 @@ import (
"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"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -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, types.ServiceInspectOptions{InsertDefaults: true})
service, _, err := client.ServiceInspectWithRaw(ctx, ref, swarm.ServiceInspectOptions{InsertDefaults: true})
if err == nil || !cerrdefs.IsNotFound(err) {
return service, nil, err
}

View File

@ -9,7 +9,6 @@ import (
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
@ -57,7 +56,7 @@ func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) er
err error
)
listOpts := types.ServiceListOptions{
listOpts := swarm.ServiceListOptions{
Filters: options.filter.Value(),
// When not running "quiet", also get service status (number of running
// and desired tasks). Note that this is only supported on API v1.41 and
@ -147,7 +146,7 @@ func AppendServiceStatus(ctx context.Context, c client.APIClient, services []swa
return services, nil
}
tasks, err := c.TaskList(ctx, types.TaskListOptions{Filters: taskFilter})
tasks, err := c.TaskList(ctx, swarm.TaskListOptions{Filters: taskFilter})
if err != nil {
return nil, err
}
@ -184,7 +183,7 @@ func AppendServiceStatus(ctx context.Context, c client.APIClient, services []swa
}
func getActiveNodes(ctx context.Context, c client.NodeAPIClient) (map[string]struct{}, error) {
nodes, err := c.NodeList(ctx, types.NodeListOptions{})
nodes, err := c.NodeList(ctx, swarm.NodeListOptions{})
if err != nil {
return nil, err
}

View File

@ -9,7 +9,6 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"gotest.tools/v3/assert"
@ -19,7 +18,7 @@ import (
func TestServiceListOrder(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
newService("a57dbe8", "service-1-foo"),
newService("a57dbdd", "service-10-foo"),
@ -173,7 +172,7 @@ func TestServiceListServiceStatus(t *testing.T) {
tc.cluster = generateCluster(t, tc.opts)
}
cli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
if !options.Status || versions.LessThan(tc.opts.apiVersion, "1.41") {
// Don't return "ServiceStatus" if not requested, or on older API versions
for i := range tc.cluster.services {
@ -182,10 +181,10 @@ func TestServiceListServiceStatus(t *testing.T) {
}
return tc.cluster.services, nil
},
taskListFunc: func(context.Context, types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(context.Context, swarm.TaskListOptions) ([]swarm.Task, error) {
return tc.cluster.tasks, nil
},
nodeListFunc: func(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
nodeListFunc: func(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
return tc.cluster.nodes, nil
},
})

View File

@ -15,7 +15,6 @@ import (
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/internal/logdetails"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
@ -91,7 +90,7 @@ func runLogs(ctx context.Context, dockerCli command.Cli, opts *logsOptions) erro
logfunc func(context.Context, string, container.LogsOptions) (io.ReadCloser, error)
)
service, _, err := apiClient.ServiceInspectWithRaw(ctx, opts.target, types.ServiceInspectOptions{})
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) {

View File

@ -11,7 +11,6 @@ import (
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
@ -89,7 +88,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
)
for {
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, swarm.ServiceInspectOptions{})
if err != nil {
return err
}
@ -143,7 +142,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
return nil
}
tasks, err := apiClient.TaskList(ctx, types.TaskListOptions{Filters: filters.NewArgs(
tasks, err := apiClient.TaskList(ctx, swarm.TaskListOptions{Filters: filters.NewArgs(
filters.KeyValuePair{Key: "service", Value: service.ID},
filters.KeyValuePair{Key: "_up-to-date", Value: "true"},
)})
@ -217,7 +216,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
//
// TODO(thaJeztah): this should really be a filter on [apiClient.NodeList] instead of being filtered on the client side.
func getActiveNodes(ctx context.Context, apiClient client.NodeAPIClient) (map[string]struct{}, error) {
nodes, err := apiClient.NodeList(ctx, types.NodeListOptions{})
nodes, err := apiClient.NodeList(ctx, swarm.NodeListOptions{})
if err != nil {
return nil, err
}

View File

@ -11,8 +11,8 @@ import (
"github.com/docker/cli/cli/command/node"
"github.com/docker/cli/cli/command/task"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -68,7 +68,7 @@ func runPS(ctx context.Context, dockerCli command.Cli, options psOptions) error
return err
}
tasks, err := apiClient.TaskList(ctx, types.TaskListOptions{Filters: filter})
tasks, err := apiClient.TaskList(ctx, swarm.TaskListOptions{Filters: filter})
if err != nil {
return err
}
@ -98,11 +98,11 @@ func createFilter(ctx context.Context, apiClient client.APIClient, options psOpt
serviceIDFilter.Add("id", service)
serviceNameFilter.Add("name", service)
}
serviceByIDList, err := apiClient.ServiceList(ctx, types.ServiceListOptions{Filters: serviceIDFilter})
serviceByIDList, err := apiClient.ServiceList(ctx, swarm.ServiceListOptions{Filters: serviceIDFilter})
if err != nil {
return filter, nil, err
}
serviceByNameList, err := apiClient.ServiceList(ctx, types.ServiceListOptions{Filters: serviceNameFilter})
serviceByNameList, err := apiClient.ServiceList(ctx, swarm.ServiceListOptions{Filters: serviceNameFilter})
if err != nil {
return filter, nil, err
}

View File

@ -6,7 +6,6 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
@ -17,7 +16,7 @@ import (
func TestCreateFilter(t *testing.T) {
client := &fakeClient{
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
{ID: "idmatch"},
{ID: "idprefixmatch"},
@ -49,7 +48,7 @@ func TestCreateFilter(t *testing.T) {
func TestCreateFilterWithAmbiguousIDPrefixError(t *testing.T) {
client := &fakeClient{
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
{ID: "aaaone"},
{ID: "aaatwo"},
@ -76,7 +75,7 @@ func TestCreateFilterNoneFound(t *testing.T) {
func TestRunPSWarnsOnNotFound(t *testing.T) {
client := &fakeClient{
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
{ID: "foo"},
}, nil
@ -97,10 +96,10 @@ func TestRunPSWarnsOnNotFound(t *testing.T) {
func TestRunPSQuiet(t *testing.T) {
client := &fakeClient{
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{{ID: "foo"}}, nil
},
taskListFunc: func(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(ctx context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{{ID: "sxabyp0obqokwekpun4rjo0b3"}}, nil
},
}

View File

@ -7,7 +7,7 @@ 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"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -43,12 +43,12 @@ func newRollbackCommand(dockerCli command.Cli) *cobra.Command {
func runRollback(ctx context.Context, dockerCLI command.Cli, options *serviceOptions, serviceID string) error {
apiClient := dockerCLI.Client()
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, swarm.ServiceInspectOptions{})
if err != nil {
return err
}
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, swarm.ServiceUpdateOptions{
Rollback: "previous", // TODO(thaJeztah): this should have a const defined
})
if err != nil {

View File

@ -8,7 +8,6 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -18,7 +17,7 @@ func TestRollback(t *testing.T) {
testCases := []struct {
name string
args []string
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
expectedDockerCliErr string
}{
{
@ -28,7 +27,7 @@ func TestRollback(t *testing.T) {
{
name: "rollback-service-with-warnings",
args: []string{"service-id"},
serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
response := swarm.ServiceUpdateResponse{}
response.Warnings = []string{
@ -59,8 +58,8 @@ func TestRollbackWithErrors(t *testing.T) {
testCases := []struct {
name string
args []string
serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error)
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error)
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
expectedError string
}{
{
@ -75,7 +74,7 @@ func TestRollbackWithErrors(t *testing.T) {
{
name: "service-does-not-exists",
args: []string{"service-id"},
serviceInspectWithRawFunc: func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
serviceInspectWithRawFunc: func(ctx context.Context, serviceID string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
return swarm.Service{}, []byte{}, fmt.Errorf("no such services: %s", serviceID)
},
expectedError: "no such services: service-id",
@ -83,7 +82,7 @@ func TestRollbackWithErrors(t *testing.T) {
{
name: "service-update-failed",
args: []string{"service-id"},
serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
return swarm.ServiceUpdateResponse{}, fmt.Errorf("no such services: %s", serviceID)
},
expectedError: "no such services: service-id",

View File

@ -9,7 +9,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/spf13/cobra"
@ -94,7 +94,7 @@ func runScale(ctx context.Context, dockerCLI command.Cli, options *scaleOptions,
}
func runServiceScale(ctx context.Context, apiClient client.ServiceAPIClient, serviceID string, scale uint64) (warnings []string, _ error) {
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, swarm.ServiceInspectOptions{})
if err != nil {
return nil, err
}
@ -109,7 +109,7 @@ func runServiceScale(ctx context.Context, apiClient client.ServiceAPIClient, ser
return nil, errors.New("scale can only be used with replicated or replicated-job mode")
}
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{})
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, swarm.ServiceUpdateOptions{})
if err != nil {
return nil, err
}

View File

@ -12,7 +12,6 @@ import (
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
"github.com/docker/cli/opts/swarmopts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
@ -156,7 +155,7 @@ func newListOptsVarWithValidator(validator opts.ValidatorFctType) *opts.ListOpts
func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error {
apiClient := dockerCLI.Client()
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, swarm.ServiceInspectOptions{})
if err != nil {
return err
}
@ -201,7 +200,7 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet,
}
}
updateOpts := types.ServiceUpdateOptions{}
updateOpts := swarm.ServiceUpdateOptions{}
if serverSideRollback {
updateOpts.Rollback = "previous"
}
@ -255,9 +254,9 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet,
}
updateOpts.EncodedRegistryAuth = encodedAuth
case clientSideRollback:
updateOpts.RegistryAuthFrom = types.RegistryAuthFromPreviousSpec
updateOpts.RegistryAuthFrom = swarm.RegistryAuthFromPreviousSpec
default:
updateOpts.RegistryAuthFrom = types.RegistryAuthFromSpec
updateOpts.RegistryAuthFrom = swarm.RegistryAuthFromSpec
}
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, *spec, updateOpts)

View File

@ -28,15 +28,15 @@ type fakeClient struct {
removedSecrets []string
removedConfigs []string
serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error)
serviceListFunc func(options swarm.ServiceListOptions) ([]swarm.Service, error)
networkListFunc func(options network.ListOptions) ([]network.Summary, error)
secretListFunc func(options swarm.SecretListOptions) ([]swarm.Secret, error)
configListFunc func(options swarm.ConfigListOptions) ([]swarm.Config, error)
nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error)
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
nodeListFunc func(options swarm.NodeListOptions) ([]swarm.Node, error)
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error)
serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
serviceRemoveFunc func(serviceID string) error
networkRemoveFunc func(networkID string) error
@ -55,7 +55,7 @@ func (cli *fakeClient) ClientVersion() string {
return cli.version
}
func (cli *fakeClient) ServiceList(_ context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
func (cli *fakeClient) ServiceList(_ context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
if cli.serviceListFunc != nil {
return cli.serviceListFunc(options)
}
@ -115,14 +115,14 @@ func (cli *fakeClient) ConfigList(_ context.Context, options swarm.ConfigListOpt
return configsList, nil
}
func (cli *fakeClient) TaskList(_ context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
func (cli *fakeClient) TaskList(_ context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
if cli.taskListFunc != nil {
return cli.taskListFunc(options)
}
return []swarm.Task{}, nil
}
func (cli *fakeClient) NodeList(_ context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
func (cli *fakeClient) NodeList(_ context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
if cli.nodeListFunc != nil {
return cli.nodeListFunc(options)
}
@ -136,7 +136,7 @@ func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, ref string) (swarm.
return swarm.Node{}, nil, nil
}
func (cli *fakeClient) ServiceUpdate(_ context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
func (cli *fakeClient) ServiceUpdate(_ context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
if cli.serviceUpdateFunc != nil {
return cli.serviceUpdateFunc(serviceID, version, service, options)
}
@ -180,7 +180,7 @@ func (cli *fakeClient) ConfigRemove(_ context.Context, configID string) error {
return nil
}
func (*fakeClient) ServiceInspectWithRaw(_ context.Context, serviceID string, _ types.ServiceInspectOptions) (swarm.Service, []byte, error) {
func (*fakeClient) ServiceInspectWithRaw(_ context.Context, serviceID string, _ swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
return swarm.Service{
ID: serviceID,
Spec: swarm.ServiceSpec{

View File

@ -7,7 +7,6 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
@ -17,7 +16,7 @@ func TestListErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error)
serviceListFunc func(options swarm.ServiceListOptions) ([]swarm.Service, error)
expectedError string
}{
{
@ -33,14 +32,14 @@ func TestListErrors(t *testing.T) {
},
{
args: []string{},
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{}, errors.New("error getting services")
},
expectedError: "error getting services",
},
{
args: []string{},
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{*builders.Service()}, nil
},
expectedError: "cannot get label",
@ -115,7 +114,7 @@ func TestStackList(t *testing.T) {
)
}
cli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return services, nil
},
})

View File

@ -9,7 +9,6 @@ import (
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -19,7 +18,7 @@ import (
func TestStackPsErrors(t *testing.T) {
testCases := []struct {
args []string
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
expectedError string
}{
{
@ -32,7 +31,7 @@ func TestStackPsErrors(t *testing.T) {
},
{
args: []string{"foo"},
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return nil, errors.New("error getting tasks")
},
expectedError: "error getting tasks",
@ -55,7 +54,7 @@ func TestStackPsErrors(t *testing.T) {
func TestStackPs(t *testing.T) {
testCases := []struct {
doc string
taskListFunc func(types.TaskListOptions) ([]swarm.Task, error)
taskListFunc func(swarm.TaskListOptions) ([]swarm.Task, error)
nodeInspectWithRaw func(string) (swarm.Node, []byte, error)
config configfile.ConfigFile
args []string
@ -70,7 +69,7 @@ func TestStackPs(t *testing.T) {
},
{
doc: "WithEmptyStack",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{}, nil
},
args: []string{"foo"},
@ -78,7 +77,7 @@ func TestStackPs(t *testing.T) {
},
{
doc: "WithQuietOption",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*builders.Task(builders.TaskID("id-foo"))}, nil
},
args: []string{"foo"},
@ -89,7 +88,7 @@ func TestStackPs(t *testing.T) {
},
{
doc: "WithNoTruncOption",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*builders.Task(builders.TaskID("xn4cypcov06f2w8gsbaf2lst3"))}, nil
},
args: []string{"foo"},
@ -101,7 +100,7 @@ func TestStackPs(t *testing.T) {
},
{
doc: "WithNoResolveOption",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*builders.Task(
builders.TaskNodeID("id-node-foo"),
)}, nil
@ -118,7 +117,7 @@ func TestStackPs(t *testing.T) {
},
{
doc: "WithFormat",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*builders.Task(builders.TaskServiceID("service-id-foo"))}, nil
},
args: []string{"foo"},
@ -129,7 +128,7 @@ func TestStackPs(t *testing.T) {
},
{
doc: "WithConfigFormat",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*builders.Task(builders.TaskServiceID("service-id-foo"))}, nil
},
config: configfile.ConfigFile{
@ -140,7 +139,7 @@ func TestStackPs(t *testing.T) {
},
{
doc: "WithoutFormat",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*builders.Task(
builders.TaskID("id-foo"),
builders.TaskServiceID("service-id-foo"),

View File

@ -8,7 +8,6 @@ import (
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -19,37 +18,37 @@ func TestStackServicesErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error)
nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error)
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
serviceListFunc func(options swarm.ServiceListOptions) ([]swarm.Service, error)
nodeListFunc func(options swarm.NodeListOptions) ([]swarm.Node, error)
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
expectedError string
}{
{
args: []string{"foo"},
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return nil, errors.New("error getting services")
},
expectedError: "error getting services",
},
{
args: []string{"foo"},
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{*builders.Service(builders.GlobalService())}, nil
},
nodeListFunc: func(options types.NodeListOptions) ([]swarm.Node, error) {
nodeListFunc: func(options swarm.NodeListOptions) ([]swarm.Node, error) {
return nil, errors.New("error getting nodes")
},
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*builders.Task()}, nil
},
expectedError: "error getting nodes",
},
{
args: []string{"foo"},
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{*builders.Service(builders.GlobalService())}, nil
},
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
return nil, errors.New("error getting tasks")
},
expectedError: "error getting tasks",
@ -59,7 +58,7 @@ func TestStackServicesErrors(t *testing.T) {
flags: map[string]string{
"format": "{{invalid format}}",
},
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{*builders.Service()}, nil
},
expectedError: "template parsing error",
@ -96,7 +95,7 @@ func TestRunServicesWithEmptyName(t *testing.T) {
func TestStackServicesEmptyServiceList(t *testing.T) {
fakeCli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{}, nil
},
})
@ -109,7 +108,7 @@ func TestStackServicesEmptyServiceList(t *testing.T) {
func TestStackServicesWithQuietOption(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{*builders.Service(builders.ServiceID("id-foo"))}, nil
},
})
@ -122,7 +121,7 @@ func TestStackServicesWithQuietOption(t *testing.T) {
func TestStackServicesWithFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
*builders.Service(builders.ServiceName("service-name-foo")),
}, nil
@ -137,7 +136,7 @@ func TestStackServicesWithFormat(t *testing.T) {
func TestStackServicesWithConfigFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
*builders.Service(builders.ServiceName("service-name-foo")),
}, nil
@ -154,7 +153,7 @@ func TestStackServicesWithConfigFormat(t *testing.T) {
func TestStackServicesWithoutFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{*builders.Service(
builders.ServiceName("name-foo"),
builders.ServiceID("id-foo"),

View File

@ -28,15 +28,15 @@ type fakeClient struct {
removedSecrets []string
removedConfigs []string
serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error)
serviceListFunc func(options swarm.ServiceListOptions) ([]swarm.Service, error)
networkListFunc func(options network.ListOptions) ([]network.Summary, error)
secretListFunc func(options swarm.SecretListOptions) ([]swarm.Secret, error)
configListFunc func(options swarm.ConfigListOptions) ([]swarm.Config, error)
nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error)
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
nodeListFunc func(options swarm.NodeListOptions) ([]swarm.Node, error)
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error)
serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
serviceRemoveFunc func(serviceID string) error
networkRemoveFunc func(networkID string) error
@ -55,7 +55,7 @@ func (cli *fakeClient) ClientVersion() string {
return cli.version
}
func (cli *fakeClient) ServiceList(_ context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
func (cli *fakeClient) ServiceList(_ context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
if cli.serviceListFunc != nil {
return cli.serviceListFunc(options)
}
@ -115,14 +115,14 @@ func (cli *fakeClient) ConfigList(_ context.Context, options swarm.ConfigListOpt
return configsList, nil
}
func (cli *fakeClient) TaskList(_ context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
func (cli *fakeClient) TaskList(_ context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
if cli.taskListFunc != nil {
return cli.taskListFunc(options)
}
return []swarm.Task{}, nil
}
func (cli *fakeClient) NodeList(_ context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
func (cli *fakeClient) NodeList(_ context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
if cli.nodeListFunc != nil {
return cli.nodeListFunc(options)
}
@ -136,7 +136,7 @@ func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, ref string) (swarm.
return swarm.Node{}, nil, nil
}
func (cli *fakeClient) ServiceUpdate(_ context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
func (cli *fakeClient) ServiceUpdate(_ context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
if cli.serviceUpdateFunc != nil {
return cli.serviceUpdateFunc(serviceID, version, service, options)
}

View File

@ -5,7 +5,6 @@ import (
"github.com/docker/cli/cli/compose/convert"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm"
@ -31,7 +30,7 @@ func getAllStacksFilter() filters.Args {
}
func getStackServices(ctx context.Context, apiclient client.APIClient, namespace string) ([]swarm.Service, error) {
return apiclient.ServiceList(ctx, types.ServiceListOptions{Filters: getStackFilter(namespace)})
return apiclient.ServiceList(ctx, swarm.ServiceListOptions{Filters: getStackFilter(namespace)})
}
func getStackNetworks(ctx context.Context, apiclient client.APIClient, namespace string) ([]network.Summary, error) {
@ -47,5 +46,5 @@ func getStackConfigs(ctx context.Context, apiclient client.APIClient, namespace
}
func getStackTasks(ctx context.Context, apiclient client.APIClient, namespace string) ([]swarm.Task, error) {
return apiclient.TaskList(ctx, types.TaskListOptions{Filters: getStackFilter(namespace)})
return apiclient.TaskList(ctx, swarm.TaskListOptions{Filters: getStackFilter(namespace)})
}

View File

@ -11,7 +11,6 @@ import (
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/compose/convert"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm"
@ -221,7 +220,7 @@ func deployServices(ctx context.Context, dockerCLI command.Cli, services map[str
if service, exists := existingServiceMap[name]; exists {
_, _ = fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
updateOpts := swarm.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
switch resolveImage {
case ResolveImageAlways:
@ -266,7 +265,7 @@ func deployServices(ctx context.Context, dockerCLI command.Cli, services map[str
} else {
_, _ = fmt.Fprintln(out, "Creating service", name)
createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
createOpts := swarm.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
// query registry if flag disabling it was not set
if resolveImage == ResolveImageAlways || resolveImage == ResolveImageChanged {

View File

@ -6,7 +6,6 @@ import (
"github.com/docker/cli/cli/compose/convert"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -33,12 +32,12 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
namespace := convert.NewNamespace("mystack")
var (
receivedOptions types.ServiceUpdateOptions
receivedOptions swarm.ServiceUpdateOptions
receivedService swarm.ServiceSpec
)
client := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
return []swarm.Service{
{
Spec: swarm.ServiceSpec{
@ -56,7 +55,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
},
}, nil
},
serviceUpdateFunc: func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
serviceUpdateFunc: func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
receivedOptions = options
receivedService = service
return swarm.ServiceUpdateResponse{}, nil
@ -105,7 +104,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
assert.Check(t, is.Equal(receivedService.TaskTemplate.ForceUpdate, tc.expectedForceUpdate))
receivedService = swarm.ServiceSpec{}
receivedOptions = types.ServiceUpdateOptions{}
receivedOptions = swarm.ServiceUpdateOptions{}
})
}
}

View File

@ -5,7 +5,7 @@ import (
"github.com/docker/cli/cli/command/stack/formatter"
"github.com/docker/cli/cli/compose/convert"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/pkg/errors"
)
@ -14,7 +14,7 @@ import (
func GetStacks(ctx context.Context, apiClient client.ServiceAPIClient) ([]*formatter.Stack, error) {
services, err := apiClient.ServiceList(
ctx,
types.ServiceListOptions{Filters: getAllStacksFilter()})
swarm.ServiceListOptions{Filters: getAllStacksFilter()})
if err != nil {
return nil, err
}

View File

@ -8,7 +8,7 @@ import (
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/command/task"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
)
// RunPS is the swarm implementation of docker stack ps
@ -16,7 +16,7 @@ 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, types.TaskListOptions{Filters: filter})
tasks, err := client.TaskList(ctx, swarm.TaskListOptions{Filters: filter})
if err != nil {
return err
}

View File

@ -6,7 +6,6 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/service"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
)
@ -17,7 +16,7 @@ func GetServices(ctx context.Context, dockerCli command.Cli, opts options.Servic
client = dockerCli.Client()
)
listOpts := types.ServiceListOptions{
listOpts := swarm.ServiceListOptions{
Filters: getStackFilterFromOpt(opts.Namespace, opts.Filter),
// When not running "quiet", also get service status (number of running
// and desired tasks). Note that this is only supported on API v1.41 and

View File

@ -96,7 +96,7 @@ func runCA(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, opt
func updateSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet, opts caOptions) {
caCert := opts.rootCACert.Contents()
caKey := opts.rootCAKey.Contents()
opts.mergeSwarmSpecCAFlags(spec, flags, caCert)
opts.mergeSwarmSpecCAFlags(spec, flags, &caCert)
spec.CAConfig.SigningCACert = caCert
spec.CAConfig.SigningCAKey = caKey

View File

@ -69,7 +69,7 @@ func writeFile(data string) (string, error) {
if err != nil {
return "", err
}
_, err = tmpfile.Write([]byte(data))
_, err = tmpfile.WriteString(data)
if err != nil {
return "", err
}

View File

@ -3,7 +3,6 @@ package swarm
import (
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/client"
@ -12,10 +11,10 @@ import (
type fakeClient struct {
client.Client
infoFunc func() (system.Info, error)
swarmInitFunc func() (string, error)
swarmInitFunc func(req swarm.InitRequest) (string, error)
swarmInspectFunc func() (swarm.Swarm, error)
nodeInspectFunc func() (swarm.Node, []byte, error)
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
swarmJoinFunc func() error
swarmLeaveFunc func() error
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
@ -36,9 +35,9 @@ func (cli *fakeClient) NodeInspectWithRaw(context.Context, string) (swarm.Node,
return swarm.Node{}, []byte{}, nil
}
func (cli *fakeClient) SwarmInit(context.Context, swarm.InitRequest) (string, error) {
func (cli *fakeClient) SwarmInit(_ context.Context, req swarm.InitRequest) (string, error) {
if cli.swarmInitFunc != nil {
return cli.swarmInitFunc()
return cli.swarmInitFunc(req)
}
return "", nil
}
@ -50,11 +49,11 @@ func (cli *fakeClient) SwarmInspect(context.Context) (swarm.Swarm, error) {
return swarm.Swarm{}, nil
}
func (cli *fakeClient) SwarmGetUnlockKey(context.Context) (types.SwarmUnlockKeyResponse, error) {
func (cli *fakeClient) SwarmGetUnlockKey(context.Context) (swarm.UnlockKeyResponse, error) {
if cli.swarmGetUnlockKeyFunc != nil {
return cli.swarmGetUnlockKeyFunc()
}
return types.SwarmUnlockKeyResponse{}, nil
return swarm.UnlockKeyResponse{}, nil
}
func (cli *fakeClient) SwarmJoin(context.Context, swarm.JoinRequest) error {

View File

@ -4,12 +4,14 @@ import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
)
@ -17,22 +19,22 @@ func TestSwarmInitErrorOnAPIFailure(t *testing.T) {
testCases := []struct {
name string
flags map[string]string
swarmInitFunc func() (string, error)
swarmInitFunc func(swarm.InitRequest) (string, error)
swarmInspectFunc func() (swarm.Swarm, error)
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
nodeInspectFunc func() (swarm.Node, []byte, error)
expectedError string
}{
{
name: "init-failed",
swarmInitFunc: func() (string, error) {
swarmInitFunc: func(swarm.InitRequest) (string, error) {
return "", errors.New("error initializing the swarm")
},
expectedError: "error initializing the swarm",
},
{
name: "init-failed-with-ip-choice",
swarmInitFunc: func() (string, error) {
swarmInitFunc: func(swarm.InitRequest) (string, error) {
return "", errors.New("could not choose an IP address to advertise")
},
expectedError: "could not choose an IP address to advertise - specify one with --advertise-addr",
@ -56,8 +58,8 @@ func TestSwarmInitErrorOnAPIFailure(t *testing.T) {
flags: map[string]string{
flagAutolock: "true",
},
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
return types.SwarmUnlockKeyResponse{}, errors.New("error getting swarm unlock key")
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
return swarm.UnlockKeyResponse{}, errors.New("error getting swarm unlock key")
},
expectedError: "could not fetch unlock key: error getting swarm unlock key",
},
@ -86,14 +88,14 @@ func TestSwarmInit(t *testing.T) {
testCases := []struct {
name string
flags map[string]string
swarmInitFunc func() (string, error)
swarmInitFunc func(req swarm.InitRequest) (string, error)
swarmInspectFunc func() (swarm.Swarm, error)
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
nodeInspectFunc func() (swarm.Node, []byte, error)
}{
{
name: "init",
swarmInitFunc: func() (string, error) {
swarmInitFunc: func(swarm.InitRequest) (string, error) {
return "nodeID", nil
},
},
@ -102,11 +104,11 @@ func TestSwarmInit(t *testing.T) {
flags: map[string]string{
flagAutolock: "true",
},
swarmInitFunc: func() (string, error) {
swarmInitFunc: func(swarm.InitRequest) (string, error) {
return "nodeID", nil
},
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
return types.SwarmUnlockKeyResponse{
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
return swarm.UnlockKeyResponse{
UnlockKey: "unlock-key",
}, nil
},
@ -132,3 +134,28 @@ func TestSwarmInit(t *testing.T) {
})
}
}
func TestSwarmInitWithExternalCA(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
swarmInitFunc: func(req swarm.InitRequest) (string, error) {
if assert.Check(t, is.Len(req.Spec.CAConfig.ExternalCAs, 1)) {
assert.Equal(t, req.Spec.CAConfig.ExternalCAs[0].CACert, cert)
assert.Equal(t, req.Spec.CAConfig.ExternalCAs[0].Protocol, swarm.ExternalCAProtocolCFSSL)
assert.Equal(t, req.Spec.CAConfig.ExternalCAs[0].URL, "https://example.com")
}
return "nodeID", nil
},
})
tempDir := t.TempDir()
certFile := filepath.Join(tempDir, "cert.pem")
err := os.WriteFile(certFile, []byte(cert), 0o644)
assert.NilError(t, err)
cmd := newInitCommand(cli)
cmd.SetArgs([]string{})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
assert.NilError(t, cmd.Flags().Set(flagExternalCA, "protocol=cfssl,url=https://example.com,cacert="+certFile))
assert.NilError(t, cmd.Execute())
}

View File

@ -231,7 +231,7 @@ func addSwarmFlags(flags *pflag.FlagSet, options *swarmOptions) {
addSwarmCAFlags(flags, &options.swarmCAOptions)
}
func (o *swarmOptions) mergeSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet, caCert string) {
func (o *swarmOptions) mergeSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet, caCert *string) {
if flags.Changed(flagTaskHistoryLimit) {
spec.Orchestration.TaskHistoryRetentionLimit = &o.taskHistoryLimit
}
@ -255,20 +255,24 @@ type swarmCAOptions struct {
externalCA ExternalCAOption
}
func (o *swarmCAOptions) mergeSwarmSpecCAFlags(spec *swarm.Spec, flags *pflag.FlagSet, caCert string) {
func (o *swarmCAOptions) mergeSwarmSpecCAFlags(spec *swarm.Spec, flags *pflag.FlagSet, caCert *string) {
if flags.Changed(flagCertExpiry) {
spec.CAConfig.NodeCertExpiry = o.nodeCertExpiry
}
if flags.Changed(flagExternalCA) {
spec.CAConfig.ExternalCAs = o.externalCA.Value()
for _, ca := range spec.CAConfig.ExternalCAs {
ca.CACert = caCert
if caCert != nil {
for _, ca := range spec.CAConfig.ExternalCAs {
if ca.CACert == "" {
ca.CACert = *caCert
}
}
}
}
}
func (o *swarmOptions) ToSpec(flags *pflag.FlagSet) swarm.Spec {
var spec swarm.Spec
o.mergeSwarmSpec(&spec, flags, "")
o.mergeSwarmSpec(&spec, flags, nil)
return spec
}

View File

@ -8,7 +8,6 @@ import (
"os/signal"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/progress"
@ -52,7 +51,7 @@ func RootRotationProgress(ctx context.Context, dclient client.APIClient, progres
return nil
}
nodes, err := dclient.NodeList(ctx, types.NodeListOptions{})
nodes, err := dclient.NodeList(ctx, swarm.NodeListOptions{})
if err != nil {
return err
}

View File

@ -50,7 +50,7 @@ func runUnlock(ctx context.Context, dockerCli command.Cli) error {
return errors.New("Error: This node is not part of a swarm")
case swarm.LocalNodeStateLocked:
break
default:
case swarm.LocalNodeStatePending, swarm.LocalNodeStateActive, swarm.LocalNodeStateError:
return errors.New("Error: swarm is not locked")
}
@ -58,11 +58,10 @@ func runUnlock(ctx context.Context, dockerCli command.Cli) error {
if err != nil {
return err
}
req := swarm.UnlockRequest{
UnlockKey: key,
}
return client.SwarmUnlock(ctx, req)
return client.SwarmUnlock(ctx, swarm.UnlockRequest{
UnlockKey: key,
})
}
func readKey(in *streams.In, prompt string) (string, error) {

View File

@ -8,7 +8,6 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
@ -21,7 +20,7 @@ func TestSwarmUnlockKeyErrors(t *testing.T) {
flags map[string]string
swarmInspectFunc func() (swarm.Swarm, error)
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
expectedError string
}{
{
@ -64,17 +63,15 @@ func TestSwarmUnlockKeyErrors(t *testing.T) {
},
{
name: "swarm-get-unlock-key-failed",
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
return types.SwarmUnlockKeyResponse{}, errors.New("error getting unlock key")
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
return swarm.UnlockKeyResponse{}, errors.New("error getting unlock key")
},
expectedError: "error getting unlock key",
},
{
name: "swarm-no-unlock-key-failed",
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
return types.SwarmUnlockKeyResponse{
UnlockKey: "",
}, nil
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
return swarm.UnlockKeyResponse{}, nil
},
expectedError: "no unlock key is set",
},
@ -108,12 +105,12 @@ func TestSwarmUnlockKey(t *testing.T) {
flags map[string]string
swarmInspectFunc func() (swarm.Swarm, error)
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
}{
{
name: "unlock-key",
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
return types.SwarmUnlockKeyResponse{
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
return swarm.UnlockKeyResponse{
UnlockKey: "unlock-key",
}, nil
},
@ -123,8 +120,8 @@ func TestSwarmUnlockKey(t *testing.T) {
flags: map[string]string{
flagQuiet: "true",
},
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
return types.SwarmUnlockKeyResponse{
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
return swarm.UnlockKeyResponse{
UnlockKey: "unlock-key",
}, nil
},
@ -137,8 +134,8 @@ func TestSwarmUnlockKey(t *testing.T) {
swarmInspectFunc: func() (swarm.Swarm, error) {
return *builders.Swarm(builders.Autolock()), nil
},
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
return types.SwarmUnlockKeyResponse{
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
return swarm.UnlockKeyResponse{
UnlockKey: "unlock-key",
}, nil
},
@ -152,8 +149,8 @@ func TestSwarmUnlockKey(t *testing.T) {
swarmInspectFunc: func() (swarm.Swarm, error) {
return *builders.Swarm(builders.Autolock()), nil
},
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
return types.SwarmUnlockKeyResponse{
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
return swarm.UnlockKeyResponse{
UnlockKey: "unlock-key",
}, nil
},

View File

@ -53,7 +53,7 @@ func runUpdate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
prevAutoLock := swarmInspect.Spec.EncryptionConfig.AutoLockManagers
opts.mergeSwarmSpec(&swarmInspect.Spec, flags, swarmInspect.ClusterInfo.TLSInfo.TrustRoot)
opts.mergeSwarmSpec(&swarmInspect.Spec, flags, &swarmInspect.ClusterInfo.TLSInfo.TrustRoot)
curAutoLock := swarmInspect.Spec.EncryptionConfig.AutoLockManagers

View File

@ -9,7 +9,6 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
@ -22,7 +21,7 @@ func TestSwarmUpdateErrors(t *testing.T) {
flags map[string]string
swarmInspectFunc func() (swarm.Swarm, error)
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
expectedError string
}{
{
@ -58,8 +57,8 @@ func TestSwarmUpdateErrors(t *testing.T) {
swarmInspectFunc: func() (swarm.Swarm, error) {
return *builders.Swarm(), nil
},
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
return types.SwarmUnlockKeyResponse{}, errors.New("error getting unlock key")
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
return swarm.UnlockKeyResponse{}, errors.New("error getting unlock key")
},
expectedError: "error getting unlock key",
},
@ -97,7 +96,7 @@ func TestSwarmUpdate(t *testing.T) {
flags map[string]string
swarmInspectFunc func() (swarm.Swarm, error)
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
}{
{
name: "noargs",
@ -164,8 +163,8 @@ func TestSwarmUpdate(t *testing.T) {
swarmInspectFunc: func() (swarm.Swarm, error) {
return *builders.Swarm(), nil
},
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
return types.SwarmUnlockKeyResponse{
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
return swarm.UnlockKeyResponse{
UnlockKey: "unlock-key",
}, nil
},

View File

@ -26,7 +26,7 @@ type fakeClient struct {
infoFunc func(ctx context.Context) (system.Info, error)
networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error)
networkPruneFunc func(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error)
nodeListFunc func(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
nodeListFunc func(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error)
serverVersion func(ctx context.Context) (types.Version, error)
volumeListFunc func(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error)
}
@ -81,7 +81,7 @@ func (cli *fakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Ar
return network.PruneReport{}, nil
}
func (cli *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
func (cli *fakeClient) NodeList(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
if cli.nodeListFunc != nil {
return cli.nodeListFunc(ctx, options)
}

View File

@ -4,10 +4,10 @@ import (
"strings"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/volume"
"github.com/spf13/cobra"
)
@ -211,7 +211,7 @@ func networkNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []
// nodeNames contacts the API to get a list of node names.
// In case of an error, an empty list is returned.
func nodeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
list, err := dockerCLI.Client().NodeList(cmd.Context(), types.NodeListOptions{})
list, err := dockerCLI.Client().NodeList(cmd.Context(), swarm.NodeListOptions{})
if err != nil {
return []string{}
}

View File

@ -8,7 +8,6 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
@ -111,7 +110,7 @@ func TestCompleteEventFilter(t *testing.T) {
},
{
client: &fakeClient{
nodeListFunc: func(_ context.Context, _ types.NodeListOptions) ([]swarm.Node, error) {
nodeListFunc: func(_ context.Context, _ swarm.NodeListOptions) ([]swarm.Node, error) {
return []swarm.Node{
*builders.Node(builders.Hostname("n1")),
}, nil
@ -122,7 +121,7 @@ func TestCompleteEventFilter(t *testing.T) {
},
{
client: &fakeClient{
nodeListFunc: func(_ context.Context, _ types.NodeListOptions) ([]swarm.Node, error) {
nodeListFunc: func(_ context.Context, _ swarm.NodeListOptions) ([]swarm.Node, error) {
return []swarm.Node{}, errors.New("API error")
},
},

View File

@ -15,9 +15,9 @@ import (
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/inspect"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -137,7 +137,7 @@ func inspectNode(ctx context.Context, dockerCli command.Cli) inspect.GetRefFunc
func inspectService(ctx context.Context, dockerCli command.Cli) inspect.GetRefFunc {
return func(ref string) (any, []byte, error) {
// Service inspect shows defaults values in empty fields.
return dockerCli.Client().ServiceInspectWithRaw(ctx, ref, types.ServiceInspectOptions{InsertDefaults: true})
return dockerCli.Client().ServiceInspectWithRaw(ctx, ref, swarm.ServiceInspectOptions{InsertDefaults: true})
}
}

View File

@ -3,7 +3,6 @@ package task
import (
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
)
@ -11,7 +10,7 @@ import (
type fakeClient struct {
client.APIClient
nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error)
serviceInspectWithRaw func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error)
serviceInspectWithRaw func(ref string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error)
}
func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, ref string) (swarm.Node, []byte, error) {
@ -21,7 +20,7 @@ func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, ref string) (swarm.
return swarm.Node{}, nil, nil
}
func (cli *fakeClient) ServiceInspectWithRaw(_ context.Context, ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
func (cli *fakeClient) ServiceInspectWithRaw(_ context.Context, ref string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
if cli.serviceInspectWithRaw != nil {
return cli.serviceInspectWithRaw(ref, options)
}

View File

@ -9,7 +9,6 @@ import (
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
@ -17,7 +16,7 @@ import (
func TestTaskPrintSorted(t *testing.T) {
apiClient := &fakeClient{
serviceInspectWithRaw: func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
serviceInspectWithRaw: func(ref string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
if ref == "service-id-one" {
return *builders.Service(builders.ServiceName("service-name-1")), nil, nil
}
@ -109,7 +108,7 @@ func TestTaskPrintWithIndentation(t *testing.T) {
const trunc = false
const noResolve = false
apiClient := &fakeClient{
serviceInspectWithRaw: func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
serviceInspectWithRaw: func(ref string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
return *builders.Service(builders.ServiceName("service-name-foo")), nil, nil
},
nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) {
@ -145,7 +144,7 @@ func TestTaskPrintWithResolution(t *testing.T) {
const trunc = false
const noResolve = false
apiClient := &fakeClient{
serviceInspectWithRaw: func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
serviceInspectWithRaw: func(ref string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
return *builders.Service(builders.ServiceName("service-name-foo")), nil, nil
},
nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) {

View File

@ -82,7 +82,10 @@ func runSignImage(ctx context.Context, dockerCLI command.Cli, options signOption
return trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), err)
}
}
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCLI, imgRefAndAuth.RepoInfo().Index, "push")
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) {

View File

@ -3,12 +3,14 @@ package configfile
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/cli/cli/config/memorystore"
"github.com/docker/cli/cli/config/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -46,6 +48,31 @@ type ConfigFile struct {
Experimental string `json:"experimental,omitempty"`
}
type configEnvAuth struct {
Auth string `json:"auth"`
}
type configEnv struct {
AuthConfigs map[string]configEnvAuth `json:"auths"`
}
// DockerEnvConfigKey is an environment variable that contains a JSON encoded
// credential config. It only supports storing the credentials as a base64
// encoded string in the format base64("username:pat").
//
// Adding additional fields will produce a parsing error.
//
// Example:
//
// {
// "auths": {
// "example.test": {
// "auth": base64-encoded-username-pat
// }
// }
// }
const DockerEnvConfigKey = "DOCKER_AUTH_CONFIG"
// ProxyConfig contains proxy configuration settings
type ProxyConfig struct {
HTTPProxy string `json:"httpProxy,omitempty"`
@ -152,7 +179,8 @@ func (configFile *ConfigFile) Save() (retErr error) {
return err
}
defer func() {
temp.Close()
// ignore error as the file may already be closed when we reach this.
_ = temp.Close()
if retErr != nil {
if err := os.Remove(temp.Name()); err != nil {
logrus.WithError(err).WithField("file", temp.Name()).Debug("Error cleaning up temp file")
@ -169,10 +197,16 @@ func (configFile *ConfigFile) Save() (retErr error) {
return errors.Wrap(err, "error closing temp file")
}
// Handle situation where the configfile is a symlink
// Handle situation where the configfile is a symlink, and allow for dangling symlinks
cfgFile := configFile.Filename
if f, err := os.Readlink(cfgFile); err == nil {
if f, err := filepath.EvalSymlinks(cfgFile); err == nil {
cfgFile = f
} else if os.IsNotExist(err) {
// extract the path from the error if the configfile does not exist or is a dangling symlink
var pathError *os.PathError
if errors.As(err, &pathError) {
cfgFile = pathError.Path
}
}
// Try copying the current config file (if any) ownership and permissions
@ -256,10 +290,64 @@ func decodeAuth(authStr string) (string, string, error) {
// GetCredentialsStore returns a new credentials store from the settings in the
// configuration file
func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) credentials.Store {
store := credentials.NewFileStore(configFile)
if helper := getConfiguredCredentialStore(configFile, registryHostname); helper != "" {
return newNativeStore(configFile, helper)
store = newNativeStore(configFile, helper)
}
return credentials.NewFileStore(configFile)
envConfig := os.Getenv(DockerEnvConfigKey)
if envConfig == "" {
return store
}
authConfig, err := parseEnvConfig(envConfig)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "Failed to create credential store from DOCKER_AUTH_CONFIG: ", err)
return store
}
// use DOCKER_AUTH_CONFIG if set
// it uses the native or file store as a fallback to fetch and store credentials
envStore, err := memorystore.New(
memorystore.WithAuthConfig(authConfig),
memorystore.WithFallbackStore(store),
)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "Failed to create credential store from DOCKER_AUTH_CONFIG: ", err)
return store
}
return envStore
}
func parseEnvConfig(v string) (map[string]types.AuthConfig, error) {
envConfig := &configEnv{}
decoder := json.NewDecoder(strings.NewReader(v))
decoder.DisallowUnknownFields()
if err := decoder.Decode(envConfig); err != nil && !errors.Is(err, io.EOF) {
return nil, err
}
if decoder.More() {
return nil, errors.New("DOCKER_AUTH_CONFIG does not support more than one JSON object")
}
authConfigs := make(map[string]types.AuthConfig)
for addr, envAuth := range envConfig.AuthConfigs {
if envAuth.Auth == "" {
return nil, fmt.Errorf("DOCKER_AUTH_CONFIG environment variable is missing key `auth` for %s", addr)
}
username, password, err := decodeAuth(envAuth.Auth)
if err != nil {
return nil, err
}
authConfigs[addr] = types.AuthConfig{
Username: username,
Password: password,
ServerAddress: addr,
}
}
return authConfigs, nil
}
// var for unit testing.

View File

@ -481,6 +481,133 @@ func TestLoadFromReaderWithUsernamePassword(t *testing.T) {
}
}
const envTestUserPassConfig = `{
"auths": {
"env.example.test": {
"username": "env_user",
"password": "env_pass",
"serveraddress": "env.example.test"
}
}
}`
const envTestAuthConfig = `{
"auths": {
"env.example.test": {
"auth": "ZW52X3VzZXI6ZW52X3Bhc3M="
}
}
}`
func TestGetAllCredentialsFromEnvironment(t *testing.T) {
t.Run("can parse DOCKER_AUTH_CONFIG auth field", func(t *testing.T) {
config := &ConfigFile{}
t.Setenv("DOCKER_AUTH_CONFIG", envTestAuthConfig)
authConfigs, err := config.GetAllCredentials()
assert.NilError(t, err)
expected := map[string]types.AuthConfig{
"env.example.test": {
Username: "env_user",
Password: "env_pass",
ServerAddress: "env.example.test",
},
}
assert.Check(t, is.DeepEqual(authConfigs, expected))
})
t.Run("malformed DOCKER_AUTH_CONFIG should fallback to underlying store", func(t *testing.T) {
fallbackStore := map[string]types.AuthConfig{
"fallback.example.test": {
Username: "fallback_user",
Password: "fallback_pass",
ServerAddress: "fallback.example.test",
},
}
config := &ConfigFile{
AuthConfigs: fallbackStore,
}
t.Setenv("DOCKER_AUTH_CONFIG", envTestUserPassConfig)
authConfigs, err := config.GetAllCredentials()
assert.NilError(t, err)
expected := fallbackStore
assert.Check(t, is.DeepEqual(authConfigs, expected))
})
t.Run("can fetch credentials from DOCKER_AUTH_CONFIG and underlying store", func(t *testing.T) {
configFile := New("filename")
exampleAuth := types.AuthConfig{
Username: "user",
Password: "pass",
}
configFile.AuthConfigs["foo.example.test"] = exampleAuth
t.Setenv("DOCKER_AUTH_CONFIG", envTestAuthConfig)
authConfigs, err := configFile.GetAllCredentials()
assert.NilError(t, err)
expected := map[string]types.AuthConfig{
"foo.example.test": exampleAuth,
"env.example.test": {
Username: "env_user",
Password: "env_pass",
ServerAddress: "env.example.test",
},
}
assert.Check(t, is.DeepEqual(authConfigs, expected))
fooConfig, err := configFile.GetAuthConfig("foo.example.test")
assert.NilError(t, err)
expectedAuth := expected["foo.example.test"]
assert.Check(t, is.DeepEqual(fooConfig, expectedAuth))
envConfig, err := configFile.GetAuthConfig("env.example.test")
assert.NilError(t, err)
expectedAuth = expected["env.example.test"]
assert.Check(t, is.DeepEqual(envConfig, expectedAuth))
})
t.Run("env is ignored when empty", func(t *testing.T) {
configFile := New("filename")
t.Setenv("DOCKER_AUTH_CONFIG", "")
authConfigs, err := configFile.GetAllCredentials()
assert.NilError(t, err)
assert.Check(t, is.Len(authConfigs, 0))
})
}
func TestParseEnvConfig(t *testing.T) {
t.Run("should error on unexpected fields", func(t *testing.T) {
_, err := parseEnvConfig(envTestUserPassConfig)
assert.ErrorContains(t, err, "json: unknown field \"username\"")
})
t.Run("should be able to load env credentials", func(t *testing.T) {
got, err := parseEnvConfig(envTestAuthConfig)
assert.NilError(t, err)
expected := map[string]types.AuthConfig{
"env.example.test": {
Username: "env_user",
Password: "env_pass",
ServerAddress: "env.example.test",
},
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(got, expected))
})
t.Run("should not support multiple JSON objects", func(t *testing.T) {
_, err := parseEnvConfig(`{"auths":{"env.example.test":{"auth":"something"}}}{}`)
assert.ErrorContains(t, err, "does not support more than one JSON object")
})
}
func TestSave(t *testing.T) {
configFile := New("test-save")
defer os.Remove("test-save")
@ -538,6 +665,34 @@ func TestSaveWithSymlink(t *testing.T) {
assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}"))
}
func TestSaveWithRelativeSymlink(t *testing.T) {
dir := fs.NewDir(t, t.Name(), fs.WithFile("real-config.json", `{}`))
defer dir.Remove()
symLink := dir.Join("config.json")
relativeRealFile := "real-config.json"
realFile := dir.Join(relativeRealFile)
err := os.Symlink(relativeRealFile, symLink)
assert.NilError(t, err)
configFile := New(symLink)
err = configFile.Save()
assert.NilError(t, err)
fi, err := os.Lstat(symLink)
assert.NilError(t, err)
assert.Assert(t, fi.Mode()&os.ModeSymlink != 0, "expected %s to be a symlink", symLink)
cfg, err := os.ReadFile(symLink)
assert.NilError(t, err)
assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}"))
cfg, err = os.ReadFile(realFile)
assert.NilError(t, err)
assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}"))
}
func TestPluginConfig(t *testing.T) {
configFile := New("test-plugin")
defer os.Remove("test-plugin")

View File

@ -0,0 +1,126 @@
//go:build go1.23
package memorystore
import (
"errors"
"fmt"
"maps"
"os"
"sync"
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/cli/cli/config/types"
)
var errValueNotFound = errors.New("value not found")
func IsErrValueNotFound(err error) bool {
return errors.Is(err, errValueNotFound)
}
type Config struct {
lock sync.RWMutex
memoryCredentials map[string]types.AuthConfig
fallbackStore credentials.Store
}
func (e *Config) Erase(serverAddress string) error {
e.lock.Lock()
defer e.lock.Unlock()
delete(e.memoryCredentials, serverAddress)
if e.fallbackStore != nil {
err := e.fallbackStore.Erase(serverAddress)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "memorystore: ", err)
}
}
return nil
}
func (e *Config) Get(serverAddress string) (types.AuthConfig, error) {
e.lock.RLock()
defer e.lock.RUnlock()
authConfig, ok := e.memoryCredentials[serverAddress]
if !ok {
if e.fallbackStore != nil {
return e.fallbackStore.Get(serverAddress)
}
return types.AuthConfig{}, errValueNotFound
}
return authConfig, nil
}
func (e *Config) GetAll() (map[string]types.AuthConfig, error) {
e.lock.RLock()
defer e.lock.RUnlock()
creds := make(map[string]types.AuthConfig)
if e.fallbackStore != nil {
fileCredentials, err := e.fallbackStore.GetAll()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "memorystore: ", err)
} else {
creds = fileCredentials
}
}
maps.Copy(creds, e.memoryCredentials)
return creds, nil
}
func (e *Config) Store(authConfig types.AuthConfig) error {
e.lock.Lock()
defer e.lock.Unlock()
e.memoryCredentials[authConfig.ServerAddress] = authConfig
if e.fallbackStore != nil {
return e.fallbackStore.Store(authConfig)
}
return nil
}
// WithFallbackStore sets a fallback store.
//
// Write operations will be performed on both the memory store and the
// fallback store.
//
// Read operations will first check the memory store, and if the credential
// is not found, it will then check the fallback store.
//
// Retrieving all credentials will return from both the memory store and the
// fallback store, merging the results from both stores into a single map.
//
// Data stored in the memory store will take precedence over data in the
// fallback store.
func WithFallbackStore(store credentials.Store) Options {
return func(s *Config) error {
s.fallbackStore = store
return nil
}
}
// WithAuthConfig allows to set the initial credentials in the memory store.
func WithAuthConfig(config map[string]types.AuthConfig) Options {
return func(s *Config) error {
s.memoryCredentials = config
return nil
}
}
type Options func(*Config) error
// New creates a new in memory credential store
func New(opts ...Options) (credentials.Store, error) {
m := &Config{
memoryCredentials: make(map[string]types.AuthConfig),
}
for _, opt := range opts {
if err := opt(m); err != nil {
return nil, err
}
}
return m, nil
}

View File

@ -0,0 +1,131 @@
package memorystore
import (
"testing"
"github.com/docker/cli/cli/config/types"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestMemoryStore(t *testing.T) {
config := map[string]types.AuthConfig{
"https://example.test": {
Username: "something-something",
ServerAddress: "https://example.test",
Auth: "super_secret_token",
},
}
fallbackConfig := map[string]types.AuthConfig{
"https://only-in-file.example.test": {
Username: "something-something",
ServerAddress: "https://only-in-file.example.test",
Auth: "super_secret_token",
},
}
fallbackStore, err := New(WithAuthConfig(fallbackConfig))
assert.NilError(t, err)
memoryStore, err := New(WithAuthConfig(config), WithFallbackStore(fallbackStore))
assert.NilError(t, err)
t.Run("get credentials from memory store", func(t *testing.T) {
c, err := memoryStore.Get("https://example.test")
assert.NilError(t, err)
assert.Equal(t, c, config["https://example.test"])
})
t.Run("get credentials from fallback store", func(t *testing.T) {
c, err := memoryStore.Get("https://only-in-file.example.test")
assert.NilError(t, err)
assert.Equal(t, c, fallbackConfig["https://only-in-file.example.test"])
})
t.Run("storing credentials in memory store should also be in defined fallback store", func(t *testing.T) {
err := memoryStore.Store(types.AuthConfig{
Username: "not-in-store",
ServerAddress: "https://not-in-store.example.test",
Auth: "not-in-store_token",
})
assert.NilError(t, err)
c, err := memoryStore.Get("https://not-in-store.example.test")
assert.NilError(t, err)
assert.Equal(t, c.Username, "not-in-store")
assert.Equal(t, c.ServerAddress, "https://not-in-store.example.test")
assert.Equal(t, c.Auth, "not-in-store_token")
cc, err := fallbackStore.Get("https://not-in-store.example.test")
assert.NilError(t, err)
assert.Equal(t, cc.Username, "not-in-store")
assert.Equal(t, cc.ServerAddress, "https://not-in-store.example.test")
assert.Equal(t, cc.Auth, "not-in-store_token")
})
t.Run("delete credentials should remove credentials from memory store and fallback store", func(t *testing.T) {
err := memoryStore.Store(types.AuthConfig{
Username: "a-new-credential",
ServerAddress: "https://a-new-credential.example.test",
Auth: "a-new-credential_token",
})
assert.NilError(t, err)
err = memoryStore.Erase("https://a-new-credential.example.test")
assert.NilError(t, err)
_, err = memoryStore.Get("https://a-new-credential.example.test")
assert.Check(t, is.ErrorIs(err, errValueNotFound))
_, err = fallbackStore.Get("https://a-new-credential.example.test")
assert.Check(t, is.ErrorIs(err, errValueNotFound))
})
}
func TestMemoryStoreWithoutFallback(t *testing.T) {
config := map[string]types.AuthConfig{
"https://example.test": {
Username: "something-something",
ServerAddress: "https://example.test",
Auth: "super_secret_token",
},
}
memoryStore, err := New(WithAuthConfig(config))
assert.NilError(t, err)
t.Run("get credentials from memory store without fallback", func(t *testing.T) {
c, err := memoryStore.Get("https://example.test")
assert.NilError(t, err)
assert.Equal(t, c, config["https://example.test"])
})
t.Run("get non-existing credentials from memory store should error", func(t *testing.T) {
_, err := memoryStore.Get("https://not-in-store.example.test")
assert.Check(t, is.ErrorIs(err, errValueNotFound))
})
t.Run("case store credentials", func(t *testing.T) {
err := memoryStore.Store(types.AuthConfig{
Username: "not-in-store",
ServerAddress: "https://not-in-store.example.test",
Auth: "not-in-store_token",
})
assert.NilError(t, err)
c, err := memoryStore.Get("https://not-in-store.example.test")
assert.NilError(t, err)
assert.Equal(t, c.Username, "not-in-store")
assert.Equal(t, c.ServerAddress, "https://not-in-store.example.test")
assert.Equal(t, c.Auth, "not-in-store_token")
})
t.Run("delete credentials should remove credentials from memory store", func(t *testing.T) {
err := memoryStore.Store(types.AuthConfig{
Username: "a-new-credential",
ServerAddress: "https://a-new-credential.example.test",
Auth: "a-new-credential_token",
})
assert.NilError(t, err)
err = memoryStore.Erase("https://a-new-credential.example.test")
assert.NilError(t, err)
_, err = memoryStore.Get("https://a-new-credential.example.test")
assert.Check(t, is.ErrorIs(err, errValueNotFound))
})
}

View File

@ -47,14 +47,19 @@ func getConnectionHelper(daemonURL string, sshFlags []string) (*ConnectionHelper
}
sshFlags = addSSHTimeout(sshFlags)
sshFlags = disablePseudoTerminalAllocation(sshFlags)
remoteCommand := []string{"docker", "system", "dial-stdio"}
socketPath := sp.Path
if strings.Trim(sp.Path, "/") != "" {
remoteCommand = []string{"docker", "--host=unix://" + socketPath, "system", "dial-stdio"}
}
sshArgs, err := sp.Command(sshFlags, remoteCommand...)
if err != nil {
return nil, err
}
return &ConnectionHelper{
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
args := []string{"docker"}
if sp.Path != "" {
args = append(args, "--host", "unix://"+sp.Path)
}
args = append(args, "system", "dial-stdio")
return commandconn.New(ctx, "ssh", append(sshFlags, sp.Args(args...)...)...)
return commandconn.New(ctx, "ssh", sshArgs...)
},
Host: "http://docker.example.com",
}, nil

View File

@ -0,0 +1,27 @@
Copyright (c) 2016, Daniel Martí. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,13 @@
// Package syntax is a fork of [mvdan.cc/sh/v3@v3.10.0/syntax].
//
// Copyright (c) 2016, Daniel Martí. All rights reserved.
//
// It is a reduced set of the package to only provide the [Quote] function,
// and contains the [LICENSE], [quote.go] and [parser.go] files at the given
// revision.
//
// [quote.go]: https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/syntax/quote.go
// [parser.go]: https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/syntax/parser.go
// [LICENSE]: https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/LICENSE
// [mvdan.cc/sh/v3@v3.10.0/syntax]: https://pkg.go.dev/mvdan.cc/sh/v3@v3.10.0/syntax
package syntax

View File

@ -0,0 +1,95 @@
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
// LangVariant describes a shell language variant to use when tokenizing and
// parsing shell code. The zero value is [LangBash].
type LangVariant int
const (
// LangBash corresponds to the GNU Bash language, as described in its
// manual at https://www.gnu.org/software/bash/manual/bash.html.
//
// We currently follow Bash version 5.2.
//
// Its string representation is "bash".
LangBash LangVariant = iota
// LangPOSIX corresponds to the POSIX Shell language, as described at
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html.
//
// Its string representation is "posix" or "sh".
LangPOSIX
// LangMirBSDKorn corresponds to the MirBSD Korn Shell, also known as
// mksh, as described at http://www.mirbsd.org/htman/i386/man1/mksh.htm.
// Note that it shares some features with Bash, due to the shared
// ancestry that is ksh.
//
// We currently follow mksh version 59.
//
// Its string representation is "mksh".
LangMirBSDKorn
// LangBats corresponds to the Bash Automated Testing System language,
// as described at https://github.com/bats-core/bats-core. Note that
// it's just a small extension of the Bash language.
//
// Its string representation is "bats".
LangBats
// LangAuto corresponds to automatic language detection,
// commonly used by end-user applications like shfmt,
// which can guess a file's language variant given its filename or shebang.
//
// At this time, [Variant] does not support LangAuto.
LangAuto
)
func (l LangVariant) String() string {
switch l {
case LangBash:
return "bash"
case LangPOSIX:
return "posix"
case LangMirBSDKorn:
return "mksh"
case LangBats:
return "bats"
case LangAuto:
return "auto"
}
return "unknown shell language variant"
}
// IsKeyword returns true if the given word is part of the language keywords.
func IsKeyword(word string) bool {
// This list has been copied from the bash 5.1 source code, file y.tab.c +4460
switch word {
case
"!",
"[[", // only if COND_COMMAND is defined
"]]", // only if COND_COMMAND is defined
"case",
"coproc", // only if COPROCESS_SUPPORT is defined
"do",
"done",
"else",
"esac",
"fi",
"for",
"function",
"if",
"in",
"select", // only if SELECT_COMMAND is defined
"then",
"time", // only if COMMAND_TIMING is defined
"until",
"while",
"{",
"}":
return true
}
return false
}

View File

@ -0,0 +1,187 @@
// Copyright (c) 2021, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
type QuoteError struct {
ByteOffset int
Message string
}
func (e QuoteError) Error() string {
return fmt.Sprintf("cannot quote character at byte %d: %s", e.ByteOffset, e.Message)
}
const (
quoteErrNull = "shell strings cannot contain null bytes"
quoteErrPOSIX = "POSIX shell lacks escape sequences"
quoteErrRange = "rune out of range"
quoteErrMksh = "mksh cannot escape codepoints above 16 bits"
)
// Quote returns a quoted version of the input string,
// so that the quoted version is expanded or interpreted
// as the original string in the given language variant.
//
// Quoting is necessary when using arbitrary literal strings
// as words in a shell script or command.
// Without quoting, one can run into syntax errors,
// as well as the possibility of running unintended code.
//
// An error is returned when a string cannot be quoted for a variant.
// For instance, POSIX lacks escape sequences for non-printable characters,
// and no language variant can represent a string containing null bytes.
// In such cases, the returned error type will be *QuoteError.
//
// The quoting strategy is chosen on a best-effort basis,
// to minimize the amount of extra bytes necessary.
//
// Some strings do not require any quoting and are returned unchanged.
// Those strings can be directly surrounded in single quotes as well.
//
//nolint:gocyclo // ignore "cyclomatic complexity 35 of func `Quote` is high (> 16) (gocyclo)"
func Quote(s string, lang LangVariant) (string, error) {
if s == "" {
// Special case; an empty string must always be quoted,
// as otherwise it expands to zero fields.
return "''", nil
}
shellChars := false
nonPrintable := false
offs := 0
for rem := s; len(rem) > 0; {
r, size := utf8.DecodeRuneInString(rem)
switch r {
// Like regOps; token characters.
case ';', '"', '\'', '(', ')', '$', '|', '&', '>', '<', '`',
// Whitespace; might result in multiple fields.
' ', '\t', '\r', '\n',
// Escape sequences would be expanded.
'\\',
// Would start a comment unless quoted.
'#',
// Might result in brace expansion.
'{',
// Might result in tilde expansion.
'~',
// Might result in globbing.
'*', '?', '[',
// Might result in an assignment.
'=':
shellChars = true
case '\x00':
return "", &QuoteError{ByteOffset: offs, Message: quoteErrNull}
}
if r == utf8.RuneError || !unicode.IsPrint(r) {
if lang == LangPOSIX {
return "", &QuoteError{ByteOffset: offs, Message: quoteErrPOSIX}
}
nonPrintable = true
}
rem = rem[size:]
offs += size
}
if !shellChars && !nonPrintable && !IsKeyword(s) {
// Nothing to quote; avoid allocating.
return s, nil
}
// Single quotes are usually best,
// as they don't require any escaping of characters.
// If we have any invalid utf8 or non-printable runes,
// use $'' so that we can escape them.
// Note that we can't use double quotes for those.
var b strings.Builder
if nonPrintable {
b.WriteString("$'")
lastRequoteIfHex := false
offs = 0
for rem := s; len(rem) > 0; {
nextRequoteIfHex := false
r, size := utf8.DecodeRuneInString(rem)
switch {
case r == '\'', r == '\\':
b.WriteByte('\\')
b.WriteRune(r)
case unicode.IsPrint(r) && r != utf8.RuneError:
if lastRequoteIfHex && isHex(r) {
b.WriteString("'$'")
}
b.WriteRune(r)
case r == '\a':
b.WriteString(`\a`)
case r == '\b':
b.WriteString(`\b`)
case r == '\f':
b.WriteString(`\f`)
case r == '\n':
b.WriteString(`\n`)
case r == '\r':
b.WriteString(`\r`)
case r == '\t':
b.WriteString(`\t`)
case r == '\v':
b.WriteString(`\v`)
case r < utf8.RuneSelf, r == utf8.RuneError && size == 1:
// \xXX, fixed at two hexadecimal characters.
fmt.Fprintf(&b, "\\x%02x", rem[0])
// Unfortunately, mksh allows \x to consume more hex characters.
// Ensure that we don't allow it to read more than two.
if lang == LangMirBSDKorn {
nextRequoteIfHex = true
}
case r > utf8.MaxRune:
// Not a valid Unicode code point?
return "", &QuoteError{ByteOffset: offs, Message: quoteErrRange}
case lang == LangMirBSDKorn && r > 0xFFFD:
// From the CAVEATS section in R59's man page:
//
// mksh currently uses OPTU-16 internally, which is the same as
// UTF-8 and CESU-8 with 0000..FFFD being valid codepoints.
return "", &QuoteError{ByteOffset: offs, Message: quoteErrMksh}
case r < 0x10000:
// \uXXXX, fixed at four hexadecimal characters.
fmt.Fprintf(&b, "\\u%04x", r)
default:
// \UXXXXXXXX, fixed at eight hexadecimal characters.
fmt.Fprintf(&b, "\\U%08x", r)
}
rem = rem[size:]
lastRequoteIfHex = nextRequoteIfHex
offs += size
}
b.WriteString("'")
return b.String(), nil
}
// Single quotes without any need for escaping.
if !strings.Contains(s, "'") {
return "'" + s + "'", nil
}
// The string contains single quotes,
// so fall back to double quotes.
b.WriteByte('"')
for _, r := range s {
switch r {
case '"', '\\', '`', '$':
b.WriteByte('\\')
}
b.WriteRune(r)
}
b.WriteByte('"')
return b.String(), nil
}
func isHex(r rune) bool {
return (r >= '0' && r <= '9') ||
(r >= 'a' && r <= 'f') ||
(r >= 'A' && r <= 'F')
}

View File

@ -5,6 +5,8 @@ import (
"errors"
"fmt"
"net/url"
"github.com/docker/cli/cli/connhelper/internal/syntax"
)
// ParseURL creates a [Spec] from the given ssh URL. It returns an error if
@ -76,16 +78,106 @@ type Spec struct {
Path string
}
// Args returns args except "ssh" itself combined with optional additional command args
func (sp *Spec) Args(add ...string) []string {
// Args returns args except "ssh" itself combined with optional additional
// command and args to be executed on the remote host. It attempts to quote
// the given arguments to account for ssh executing the remote command in a
// shell. It returns nil when unable to quote the remote command.
func (sp *Spec) Args(remoteCommandAndArgs ...string) []string {
// Format the remote command to run using the ssh connection, quoting
// values where needed because ssh executes these in a POSIX shell.
remoteCommand, err := quoteCommand(remoteCommandAndArgs...)
if err != nil {
return nil
}
sshArgs, err := sp.args()
if err != nil {
return nil
}
if remoteCommand != "" {
sshArgs = append(sshArgs, remoteCommand)
}
return sshArgs
}
func (sp *Spec) args(sshFlags ...string) ([]string, error) {
var args []string
if sp.Host == "" {
return nil, errors.New("no host specified")
}
if sp.User != "" {
args = append(args, "-l", sp.User)
// Quote user, as it's obtained from the URL.
usr, err := syntax.Quote(sp.User, syntax.LangPOSIX)
if err != nil {
return nil, fmt.Errorf("invalid user: %w", err)
}
args = append(args, "-l", usr)
}
if sp.Port != "" {
args = append(args, "-p", sp.Port)
// Quote port, as it's obtained from the URL.
port, err := syntax.Quote(sp.Port, syntax.LangPOSIX)
if err != nil {
return nil, fmt.Errorf("invalid port: %w", err)
}
args = append(args, "-p", port)
}
args = append(args, "--", sp.Host)
args = append(args, add...)
return args
// We consider "sshFlags" to be "trusted", and set from code only,
// as they are not parsed from the DOCKER_HOST URL.
args = append(args, sshFlags...)
host, err := syntax.Quote(sp.Host, syntax.LangPOSIX)
if err != nil {
return nil, fmt.Errorf("invalid host: %w", err)
}
return append(args, "--", host), nil
}
// Command returns the ssh flags and arguments to execute a command
// (remoteCommandAndArgs) on the remote host. Where needed, it quotes
// values passed in remoteCommandAndArgs to account for ssh executing
// the remote command in a shell. It returns an error if no remote command
// is passed, or when unable to quote the remote command.
//
// Important: to preserve backward-compatibility, Command does not currently
// perform sanitization or quoting on the sshFlags and callers are expected
// to sanitize this argument.
func (sp *Spec) Command(sshFlags []string, remoteCommandAndArgs ...string) ([]string, error) {
if len(remoteCommandAndArgs) == 0 {
return nil, errors.New("no remote command specified")
}
sshArgs, err := sp.args(sshFlags...)
if err != nil {
return nil, err
}
remoteCommand, err := quoteCommand(remoteCommandAndArgs...)
if err != nil {
return nil, err
}
if remoteCommand != "" {
sshArgs = append(sshArgs, remoteCommand)
}
return sshArgs, nil
}
// quoteCommand returns the remote command to run using the ssh connection
// as a single string, quoting values where needed because ssh executes
// these in a POSIX shell.
func quoteCommand(commandAndArgs ...string) (string, error) {
var quotedCmd string
for i, arg := range commandAndArgs {
a, err := syntax.Quote(arg, syntax.LangPOSIX)
if err != nil {
return "", fmt.Errorf("invalid argument: %w", err)
}
if i == 0 {
quotedCmd = a
continue
}
quotedCmd += " " + a
}
// each part is quoted appropriately, so now we'll have a full
// shell command to pass off to "ssh"
return quotedCmd, nil
}

View File

@ -1,6 +1,7 @@
package ssh
import (
"strings"
"testing"
"gotest.tools/v3/assert"
@ -26,6 +27,28 @@ func TestParseURL(t *testing.T) {
Host: "example.com",
},
},
{
doc: "bare ssh URL with trailing slash",
url: "ssh://example.com/",
expectedArgs: []string{
"--", "example.com",
},
expectedSpec: Spec{
Host: "example.com",
Path: "/",
},
},
{
doc: "bare ssh URL with trailing slashes",
url: "ssh://example.com//",
expectedArgs: []string{
"--", "example.com",
},
expectedSpec: Spec{
Host: "example.com",
Path: "//",
},
},
{
doc: "bare ssh URL and remote command",
url: "ssh://example.com",
@ -34,7 +57,7 @@ func TestParseURL(t *testing.T) {
},
expectedArgs: []string{
"--", "example.com",
"docker", "system", "dial-stdio",
`docker system dial-stdio`,
},
expectedSpec: Spec{
Host: "example.com",
@ -48,7 +71,7 @@ func TestParseURL(t *testing.T) {
},
expectedArgs: []string{
"--", "example.com",
"docker", "--host", "unix:///var/run/docker.sock", "system", "dial-stdio",
`docker --host unix:///var/run/docker.sock system dial-stdio`,
},
expectedSpec: Spec{
Host: "example.com",
@ -84,6 +107,25 @@ func TestParseURL(t *testing.T) {
Path: "/var/run/docker.sock",
},
},
{
// This test is only to verify the behavior of ParseURL to
// pass through the Path as-is. Neither Spec.Args, nor
// Spec.Command use the Path field directly, and it should
// likely be deprecated.
doc: "bad path",
url: `ssh://example.com/var/run/docker.sock '$(echo hello > /hello.txt)'`,
remoteCommand: []string{
"docker", "--host", `unix:///var/run/docker.sock '$(echo hello > /hello.txt)'`, "system", "dial-stdio",
},
expectedArgs: []string{
"--", "example.com",
`docker --host "unix:///var/run/docker.sock '\$(echo hello > /hello.txt)'" system dial-stdio`,
},
expectedSpec: Spec{
Host: "example.com",
Path: `/var/run/docker.sock '$(echo hello > /hello.txt)'`,
},
},
{
doc: "malformed URL",
url: "malformed %%url",
@ -123,6 +165,21 @@ func TestParseURL(t *testing.T) {
url: "https://example.com",
expectedError: `invalid SSH URL: incorrect scheme: https`,
},
{
doc: "invalid URL with NUL character",
url: "ssh://example.com/var/run/\x00docker.sock",
expectedError: `invalid SSH URL: net/url: invalid control character in URL`,
},
{
doc: "invalid URL with newline character",
url: "ssh://example.com/var/run/docker.sock\n",
expectedError: `invalid SSH URL: net/url: invalid control character in URL`,
},
{
doc: "invalid URL with control character",
url: "ssh://example.com/var/run/\x1bdocker.sock",
expectedError: `invalid SSH URL: net/url: invalid control character in URL`,
},
}
for _, tc := range testCases {
t.Run(tc.doc, func(t *testing.T) {
@ -139,3 +196,122 @@ func TestParseURL(t *testing.T) {
})
}
}
func TestCommand(t *testing.T) {
testCases := []struct {
doc string
url string
sshFlags []string
customCmd []string
expectedCmd []string
expectedError string
}{
{
doc: "bare ssh URL",
url: "ssh://example.com",
expectedCmd: []string{
"--", "example.com",
"docker system dial-stdio",
},
},
{
doc: "bare ssh URL with trailing slash",
url: "ssh://example.com/",
expectedCmd: []string{
"--", "example.com",
"docker system dial-stdio",
},
},
{
doc: "bare ssh URL with custom ssh flags",
url: "ssh://example.com",
sshFlags: []string{"-T", "-o", "ConnectTimeout=30", "-oStrictHostKeyChecking=no"},
expectedCmd: []string{
"-T",
"-o", "ConnectTimeout=30",
"-oStrictHostKeyChecking=no",
"--", "example.com",
"docker system dial-stdio",
},
},
{
doc: "ssh URL with all options",
url: "ssh://me@example.com:10022/var/run/docker.sock",
sshFlags: []string{"-T", "-o ConnectTimeout=30"},
expectedCmd: []string{
"-l", "me",
"-p", "10022",
"-T",
"-o ConnectTimeout=30",
"--", "example.com",
"docker '--host=unix:///var/run/docker.sock' system dial-stdio",
},
},
{
doc: "bad ssh flags",
url: "ssh://example.com",
sshFlags: []string{"-T", "-o", `ConnectTimeout=30 $(echo hi > /hi.txt)`},
expectedCmd: []string{
"-T",
"-o", `ConnectTimeout=30 $(echo hi > /hi.txt)`,
"--", "example.com",
"docker system dial-stdio",
},
},
{
doc: "bad username",
url: `ssh://$(shutdown)me@example.com`,
expectedCmd: []string{
"-l", `'$(shutdown)me'`,
"--", "example.com",
"docker system dial-stdio",
},
},
{
doc: "bad hostname",
url: `ssh://$(shutdown)example.com`,
expectedCmd: []string{
"--", `'$(shutdown)example.com'`,
"docker system dial-stdio",
},
},
{
doc: "bad path",
url: `ssh://example.com/var/run/docker.sock '$(echo hello > /hello.txt)'`,
expectedCmd: []string{
"--", "example.com",
`docker "--host=unix:///var/run/docker.sock '\$(echo hello > /hello.txt)'" system dial-stdio`,
},
},
{
doc: "missing command",
url: "ssh://example.com",
customCmd: []string{},
expectedError: "no remote command specified",
},
}
for _, tc := range testCases {
t.Run(tc.doc, func(t *testing.T) {
sp, err := ParseURL(tc.url)
assert.NilError(t, err)
var commandAndArgs []string
if tc.customCmd == nil {
socketPath := sp.Path
commandAndArgs = []string{"docker", "system", "dial-stdio"}
if strings.Trim(socketPath, "/") != "" {
commandAndArgs = []string{"docker", "--host=unix://" + socketPath, "system", "dial-stdio"}
}
}
actualCmd, err := sp.Command(tc.sshFlags, commandAndArgs...)
if tc.expectedError == "" {
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(actualCmd, tc.expectedCmd), "%+#v", actualCmd)
} else {
assert.Check(t, is.Error(err, tc.expectedError))
assert.Check(t, is.Nil(actualCmd))
}
})
}
}

View File

@ -33,5 +33,8 @@ func IsEnabled() bool {
// The default is to log to the debug level which is only
// enabled when debugging is enabled.
var OTELErrorHandler otel.ErrorHandler = otel.ErrorHandlerFunc(func(err error) {
if err == nil {
return
}
logrus.WithError(err).Debug("otel error")
})

View File

@ -302,12 +302,12 @@ func tryPluginRun(ctx context.Context, dockerCli command.Cli, cmd *cobra.Command
srv, err := socket.NewPluginServer(nil)
if err == nil {
plugincmd.Env = append(plugincmd.Env, socket.EnvKey+"="+srv.Addr().String())
defer func() {
// Close the server when plugin execution is over, so that in case
// it's still open, any sockets on the filesystem are cleaned up.
_ = srv.Close()
}()
}
defer func() {
// Close the server when plugin execution is over, so that in case
// it's still open, any sockets on the filesystem are cleaned up.
_ = srv.Close()
}()
// Set additional environment variables specified by the caller.
plugincmd.Env = append(plugincmd.Env, envs...)
@ -334,7 +334,9 @@ func tryPluginRun(ctx context.Context, dockerCli command.Cli, cmd *cobra.Command
//
// Repeated invocations will result in EINVAL,
// or EBADF; but that is fine for our purposes.
_ = srv.Close()
if srv != nil {
_ = srv.Close()
}
// force the process to terminate if it hasn't already
if force {

View File

@ -1,5 +1,5 @@
variable "GO_VERSION" {
default = "1.24.3"
default = "1.24.5"
}
variable "VERSION" {
default = ""

View File

@ -1,12 +1,12 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.24.3
ARG GO_VERSION=1.24.5
ARG ALPINE_VERSION=3.21
# BUILDX_VERSION sets the version of buildx to install in the dev container.
# It must be a valid tag in the docker.io/docker/buildx-bin image repository
# on Docker Hub.
ARG BUILDX_VERSION=0.23.0
ARG BUILDX_VERSION=0.24.0
FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS golang

View File

@ -1,8 +1,8 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.24.3
ARG GO_VERSION=1.24.5
ARG ALPINE_VERSION=3.21
ARG GOLANGCI_LINT_VERSION=v2.1.2
ARG GOLANGCI_LINT_VERSION=v2.1.5
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint

View File

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.24.3
ARG GO_VERSION=1.24.5
ARG ALPINE_VERSION=3.21
ARG MODOUTDATED_VERSION=v0.8.0

Some files were not shown because too many files have changed in this diff Show More