Compare commits

...

938 Commits

Author SHA1 Message Date
3d4129b9ea Merge pull request #6644 from thaJeztah/connhelper_nowarn
Some checks failed
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 25, 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 (alpine, 29-rc, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 29-rc, local) (push) Has been cancelled
e2e / tests (debian, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 25, 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
e2e / tests (debian, 29-rc, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 29-rc, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
test / host (macos-15) (push) Has been cancelled
test / host (macos-15-intel) (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
cli/connhelper/commandcon: remove warn logs
2025-11-10 22:42:38 +01:00
d787e70a14 cli/connhelper/commandcon: remove warn logs
These were originally added in 6f61cf053a,
but at the time, the error wasn't returned. Now that it is, we shouldn't
log _and_ return the error.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-10 22:36:41 +01:00
e730f6f0f3 Merge pull request #6643 from thaJeztah/bump_modules2
vendor: github.com/moby/moby/api v1.52.0, moby/client v0.1.0
2025-11-10 22:04:44 +01:00
6ac3f93755 Merge pull request #6578 from thaJeztah/bump_otel_semconv
cli/command: update to semconv v1.37.0, otel v1.38.0
2025-11-10 22:04:29 +01:00
ebc1995f9f vendor: github.com/moby/moby/api v1.52.0, moby/client v0.1.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-10 21:57:40 +01:00
31d1a59d07 Merge pull request #6642 from vvoland/swarm-compose-work
swarm: revert compose/stack support for memory swappiness
2025-11-10 19:09:49 +01:00
ad96811f12 swarm: Add memory swap support (no stack/compose support)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-10 17:48:56 +01:00
6ba06b5fb4 Revert "cli/compose: add schema 3.14 (no changes from 3.13 yet)"
This reverts commit d0c86d39ef.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-10 17:48:55 +01:00
e0716b571f Revert "Add memory swap to swarm"
This reverts commit 71828f2792.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-10 17:48:54 +01:00
179efae8b0 Merge pull request #6641 from thaJeztah/bump_modules
vendor: github.com/moby/moby/api, moby/client master
2025-11-10 17:03:43 +01:00
4b450f113b vendor: github.com/moby/moby/api, moby/client master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-10 16:32:05 +01:00
ee244f2f44 Merge pull request #6636 from thaJeztah/add_missing_gobuild
Some checks failed
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 25, 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 (alpine, 29-rc, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 29-rc, local) (push) Has been cancelled
e2e / tests (debian, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 25, 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
e2e / tests (debian, 29-rc, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 29-rc, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
test / host (macos-15) (push) Has been cancelled
test / host (macos-15-intel) (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
cli/command/system: add missing "go:build"
2025-11-07 01:34:14 +01:00
0c101c4aa7 Merge pull request #6635 from thaJeztah/bump_modules
vendor: github.com/moby/moby/api v1.52.0-rc.1, moby/client v0.1.0-rc.1
2025-11-07 01:31:10 +01:00
1d789e4099 cli/command/system: add missing "go:build"
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 01:27:47 +01:00
b3824015d6 vendor: github.com/moby/moby/api v1.52.0-rc.1, moby/client v0.1.0-rc.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 01:26:13 +01:00
c4f240cc7d Merge pull request #6606 from dvdksn/update-libnetwork-docs-link
docs: update link to libnetwork protocol doc
2025-11-07 01:12:41 +01:00
5b443bf269 Merge pull request #6619 from dperny/swarm-memory-swap
Add memory swap to swarm
2025-11-07 01:03:26 +01:00
eaa6114d9e Merge pull request #6634 from thaJeztah/remove_replace
vendor.mod: remove replace
2025-11-07 00:55:53 +01:00
c9e6b41293 Merge pull request #6633 from dvdksn/docs-update-dd-documentation-link
chore: update broken link to restrucured docker desktop documentation
2025-11-07 00:34:43 +01:00
d67291026e vendor.mod: remove replace
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:32:57 +01:00
41088ed7d0 vendor: go.opentelemetry.io/auto/sdk v1.2.1
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:29:26 +01:00
712f569f17 vendor: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:29:26 +01:00
7736f5e606 vendor: align other otel packages to v1.38.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:29:24 +01:00
d45551dac9 cli/command: update to semconv v1.37.0, otel v1.38.0
Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:27:34 +01:00
71828f2792 Add memory swap to swarm
Adds support for setting memory swap settings on Swarm services

* Adds flags `memory-swap` and `memory-swappiness` to `docker service
create` and `docker service update` commands.
* Adds compose fields `memswap_limit` and `mem_swappiness` for `docker
stack` commands.

Signed-off-by: Drew Erny <derny@mirantis.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:24:44 +01:00
d0c86d39ef cli/compose: add schema 3.14 (no changes from 3.13 yet)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:24:44 +01:00
16d2036cde Merge pull request #6620 from austinvazquez/vendor-system-disk-usage-changes
vendor: github.com/moby/moby/api master, moby/client master
2025-11-07 00:20:18 +01:00
5039eee77f vendor: github.com/moby/moby/api master, moby/client master
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
2025-11-06 17:02:53 -06:00
cc7275c4e5 Merge pull request #6121 from thaJeztah/trust_plugin
implement `docker trust` as plugin
2025-11-06 20:29:58 +01:00
f7d6d5bdb3 Merge pull request #6632 from vvoland/update-go
update to go1.25.4
2025-11-06 17:13:16 +01:00
cee9ea67fc lint: run in go-modules mode
Prevent the linter from recursing to other modules (cmd/docker-trust),
which don't have their dependencies vendored.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-06 15:24:49 +01:00
b2aa690b26 scripts/build/binary: remove pkcs11 build tag
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-06 15:24:49 +01:00
c1a53ae7b6 cmd/docker-trust: remove dependency on cli/internal
Create a copy of the registry package to use, so that code used only
for trust can be removed from the cli/internal package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-06 15:24:49 +01:00
06914dd0ff make trust-plugin a separate module
skip cmd/docker-trust in tests, as it's a separate module.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-06 15:24:48 +01:00
c9bb291154 implement docker trust as plugin
move the `trust` subcommands to a plugin, so that the subcommands can
be installed separate from the `docker trust` integration in push/pull
(for situations where trust verification happens on the daemon side).

    make binary
    go build -o /usr/libexec/docker/cli-plugins/docker-trust ./cmd/docker-trust

    docker info
    Client:
     Version:    28.2.0-dev
     Context:    default
     Debug Mode: false
     Plugins:
      buildx: Docker Buildx (Docker Inc.)
        Version:  v0.24.0
        Path:     /usr/libexec/docker/cli-plugins/docker-buildx
      trust: Manage trust on Docker images (Docker Inc.)
        Version:  unknown-version
        Path:     /usr/libexec/docker/cli-plugins/docker-trust

    docker trust --help
    Usage:  docker trust [OPTIONS] COMMAND

    Extended build capabilities with BuildKit

    Options:
      -D, --debug   Enable debug logging

    Management Commands:
      key         Manage keys for signing Docker images
      signer      Manage entities who can sign Docker images

    Commands:
      inspect     Return low-level information about keys and signatures
      revoke      Remove trust for an image
      sign        Sign an image

    Run 'docker trust COMMAND --help' for more information on a command.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-06 15:24:46 +01:00
face4a61be Merge pull request #6631 from thaJeztah/fix_static_builds
Fix static build + CGO
2025-11-06 15:24:18 +01:00
dd70b43bc1 chore: update broken link to restrucured docker desktop documentation
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2025-11-06 14:40:16 +01:00
f2755b02d7 update to go1.25.4
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-06 14:24:03 +01:00
880ef756b7 Fix static build + CGO
Signed-off-by: Tianon Gravi <admwiggin@gmail.com>
2025-11-06 12:39:18 +01:00
de00f53cb9 Merge pull request #6629 from thaJeztah/bump_x_deps
vendor: golang.org/x/* dependencies
2025-11-06 11:22:48 +01:00
0976389e14 vendor: golang.org/x/net v0.46.0, golang.org/x/crypto v0.43.0
full diff: https://github.com/golang/net/compare/v0.39.0...v0.46.0
full diff: https://github.com/golang/crypto/compare/v0.39.0...v0.43.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 22:58:37 +01:00
c733cb0532 vendor: golang.org/x/time v0.14.0
full diff: https://github.com/golang/time/compare/v0.11.0...v0.14.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 22:47:59 +01:00
1f77c6f1c8 vendor: golang.org/x/term v0.36.0
- term: remove duplicate flag and add comment on windows
- term: allow multi-line bracketed paste to not create single line
  with verbatim LFs (fixes "x/term: multi line bracketed paste fails"
  to issue line by line commands

full diff: https://github.com/golang/term/compare/v0.32.0...v0.36.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 22:47:50 +01:00
dcce972f47 vendor: golang.org/x/text v0.30.0
full diff: https://github.com/golang/text/compare/v0.26.0...v0.30.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 22:47:22 +01:00
b11d143cd1 vendor: golang.org/x/sync v0.17.0
full diff: https://github.com/golang/sync/compare/v0.16.0...v0.17.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 22:46:32 +01:00
0c8ce84a62 vendor: golang.org/x/sys v0.37.0
full diff: https://github.com/golang/sys/compare/v0.33.0...v0.37.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 22:45:52 +01:00
1c572a10de Merge pull request #6628 from thaJeztah/swarm_enums
cli/command/service: use enum-consts defined in API
2025-11-05 17:32:30 +01:00
d9f7e4b0c8 cli/command/service: use enum-consts defined in API
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 13:41:04 +01:00
1686e45501 Merge pull request #6612 from jo-so/master
completion/zsh: Allow multiple volumes of 'volume rm'
2025-11-05 13:27:15 +01:00
050aca80a5 Merge pull request #6627 from thaJeztah/local_parsegeneric_resource
cli/command/service: parse generic resources without protobufs
2025-11-05 12:45:12 +01:00
30b02813f1 Merge pull request #6625 from thaJeztah/bump_golangci_lint
Dockerfile: update golangci-lint to v2.6.1
2025-11-05 12:40:19 +01:00
774f1d60a1 cli/command/service: parse generic resources without protobufs
This code was using swarmkit's genericresource package as intermediate;
add a local copy of that code that skips the protobufs as intermediate.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 12:26:02 +01:00
437ed4c1e4 Dockerfile: update golangci-lint to v2.6.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 12:24:12 +01:00
9fc247e0e5 Merge pull request #6626 from thaJeztah/fix_perfsprint
fix perfsprint (concat-loop) linting
2025-11-05 12:23:06 +01:00
924ed097c5 Merge pull request #6623 from thaJeztah/bump_swarmkit
vendor: github.com/moby/swarmkit/v2 v2.1.1
2025-11-05 12:22:42 +01:00
b52826a55e Merge pull request #6622 from thaJeztah/bump_platforms
vendor: github.com/containerd/platforms v1.0.0-rc.2
2025-11-05 12:22:30 +01:00
38d271a8e4 Merge pull request #6617 from thaJeztah/bump_jose
vendor: github.com/go-jose/go-jose/v4 v4.1.3
2025-11-05 12:22:08 +01:00
e6d6ddf38d Merge pull request #6616 from thaJeztah/bump_runewidth
vendor: github.com/mattn/go-runewidth v0.0.19
2025-11-05 12:21:51 +01:00
c4a28d0d3c vendor: github.com/go-jose/go-jose/v4 v4.1.3
- remove Go 1.23 support
- removes dependency on golang.org/x/crypto
- reject JWS with an unprotected critical b64 header

full diff: https://github.com/go-jose/go-jose/compare/v4.1.2...v4.1.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 11:55:19 +01:00
5b68e72ad3 Merge pull request #6624 from thaJeztah/bump_go_minimum
update minimum go version to go1.24
2025-11-05 11:47:27 +01:00
f8d0365127 fix perfsprint (concat-loop) linting
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 09:27:52 +01:00
7b624841c4 update minimum go version to go1.24
Various dependencies, including "golang.org/x/.."  started to update
the minimum required version,so we should follow suit for the next
release.

Note that the `//go:build` directives not necesserily have to be
updated, but it's good to keep them in sync until we have a go.mod
to control this.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 08:24:06 +01:00
82b47c8e57 vendor: github.com/moby/swarmkit/v2 v2.1.1
full diff: https://github.com/moby/swarmkit/compare/v2.1.0...v2.1.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 08:05:32 +01:00
3a91788135 vendor: github.com/containerd/platforms v1.0.0-rc.2
- Add WS2025 to Windows matcher and code optimizations
- use windowsMatchComparer for OSVersion match order
  Windows OS version should match based on the full OSVersion. When
  sorting a manifest, the entries should be sorted using the `Less`
  function.

full diff: https://github.com/containerd/platforms/compare/v1.0.0-rc.1...v1.0.0-rc.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 07:52:02 +01:00
0b3c028108 Merge pull request #6618 from thaJeztah/lint_mod
lint: don't disable modules
2025-11-05 07:31:43 +01:00
61d88c9519 lint: don't disable modules
prevent the linter from traversing the docker-trust plugin module

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-04 14:44:11 +01:00
9bce085b13 vendor: github.com/mattn/go-runewidth v0.0.19
full diff: https://github.com/mattn/go-runewidth/compare/v0.0.17...v0.0.19

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-04 14:28:14 +01:00
7414d73fc1 Merge pull request #6615 from thaJeztah/fix_generics
cli/command/container: fix use of generics
2025-11-04 07:26:44 -06:00
c01696ffde Merge pull request #6603 from thaJeztah/remove_trust_integration
remove support for client-side docker content trust validation
2025-11-04 14:05:39 +01:00
8d1525a011 Merge pull request #6614 from vvoland/list-tree-header-ansi
image/tree: Fix table header having escape codes when not tty
2025-11-04 14:02:28 +01:00
e0b1ab68fe cli/command/container: fix use of generics
This was introduced in dad1d367c8, which
did not add a `//go:build` constraint to enable the use of generics (`any`).

Which causes an error when used;

 could not import github.com/docker/cli/cli/command/container (-: # github.com/docker/cli/cli/command/container
 /Users/thajeztah/go/pkg/mod/github.com/docker/cli@v29.0.0-rc.2+incompatible/cli/command/container/stats.go:148:39: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-04 13:56:23 +01:00
e5f46499b3 image/tree: Fix table header having escape codes when not tty
When stdout is redirected to a non-tty there should be no ANSI escape
codes emitted.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-04 13:55:30 +01:00
d5d2ed5baa image/tree: Add test for checking ansi escape output
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-04 13:55:30 +01:00
1a261e3f50 image/tree: Use streams interface
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-04 13:55:30 +01:00
4893a5d5e3 completion/zsh: Allow multiple volumes of 'volume rm'
It is possible to give more than one volume for `docker volume rm`.

Signed-off-by: Jörg Sommer <joerg@jo-so.de>
2025-11-04 09:36:42 +01:00
30d597df10 Merge pull request #6605 from Benehiko/plugins/hidden
Plugin may set itself as hidden
2025-11-03 16:58:57 +01:00
700875b666 Plugin may set itself as hidden
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-11-03 16:36:46 +01:00
7e32fdf1b3 Merge pull request #6611 from thaJeztah/skip_hidden_plugin_commands
cli: allManagementSubCommands: improve handling of plugin stubs
2025-11-03 15:34:01 +01:00
ad776d1e10 remove support for client-side docker content trust validation
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-03 14:21:19 +01:00
259df25a96 cli: allManagementSubCommands: improve handling of plugin stubs
The allManagementSubCommands function is used to present plugin-commands
in the docker --help output; these commands are included in the "management
commands" section, but for plugins we don't know if they have sub-commands.

However, plugin stubs may be hidden (for placeholders that are not yet loaded),
or not be runnable, which was previously ignored.

This patch treats plugin-stubs the same as other commands, with the exception
of checking if they have subcommands (which is not yet known for plugin-stubs).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-03 13:57:01 +01:00
aa62a6a97a docs: update link to libnetwork protocol doc
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2025-11-03 13:32:30 +01:00
b5bac44972 Merge pull request #6601 from thaJeztah/bump_modules2
Some checks failed
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 25, 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 (alpine, 29-rc, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 29-rc, local) (push) Has been cancelled
e2e / tests (debian, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 25, 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
e2e / tests (debian, 29-rc, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 29-rc, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
test / host (macos-15) (push) Has been cancelled
test / host (macos-15-intel) (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/moby/moby/api v1.52.0-beta.4, client v0.1.0-beta.3
2025-10-31 19:36:12 +01:00
ef16d82301 vendor: github.com/moby/moby/api v1.52.0-beta.4, client v0.1.0-beta.3
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-31 19:30:20 +01:00
73aade19c1 Merge pull request #6566 from vvoland/img-list
image/list: Show collapsed tree by default
2025-10-31 18:22:07 +01:00
0f589b35c5 Merge pull request #6565 from thaJeztah/add_29
e2e: add docker v29-rc
2025-10-31 18:16:16 +01:00
6fa5900339 image/tree: Remove longest->shortest sort
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-31 18:10:19 +01:00
5836040ec9 Update golden files
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-31 18:10:19 +01:00
f6feef8fe2 image/test: Fix go test args being used by CLI commands
By default cobra inherit the `os.Args` if there's no non-nil Args slice
set.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-31 18:10:19 +01:00
631f32ee9d images/list: Add print ambiguous warning for tree
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-31 18:10:19 +01:00
c41815f17a image/list: Show collapsed tree by default
Use the new tree view by default and only fallback if format or old
view-related options are used.

The expanded view is shown when `--tree` is passed.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-31 18:10:19 +01:00
5d599e9322 e2e: add docker v29-rc
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-31 17:56:37 +01:00
cdaae144e9 Merge pull request #6600 from thaJeztah/remove_trust_e2e
e2e: remove DCT tests
2025-10-31 17:56:22 +01:00
7fb94dae5b Merge pull request #6599 from vvoland/update-docker
vendor: github.com/moby/moby master
2025-10-31 17:50:18 +01:00
50598d21c9 skip TestBuildIidFileSquash
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-31 17:45:30 +01:00
cf9e1778d3 Dockerfile: remove notary
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-31 17:45:30 +01:00
c98d9647d3 e2e: remove DCT tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-31 17:45:29 +01:00
f8e871344f Merge pull request #6596 from vvoland/img-list-all-dangling
image/tree: Fix dangling filter condition
2025-10-31 17:42:33 +01:00
173808f8b6 Merge pull request #6598 from AkihiroSuda/deprecate-cgroup1
docs: deprecated: deprecate cgroup v1
2025-10-31 17:40:42 +01:00
8444c911bd vendor: github.com/moby/moby master
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-31 17:38:52 +01:00
96bc39b36d Merge pull request #6597 from thaJeztah/use_pull_for_pull
cli/command/container: use ImagePull instead of ImageCreate
2025-10-31 17:29:13 +01:00
918ec8c48a Merge pull request #6595 from vvoland/list-tree-sortby-name
image/tree: Sort image tree by name instead of creation date
2025-10-31 16:55:07 +01:00
7f86de9319 docs: deprecated: deprecate cgroup v1
See
- moby/moby issue 51111
- moby/moby PR    51360

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-11-01 00:55:03 +09:00
7bdb4df07d cli/command/container: use ImagePull instead of ImageCreate
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-31 15:50:18 +01:00
9e7937746c image/tree: Fix dangling filter condition
The logic for applying the dangling filter when `--all` is not used was
inverted. The filter was being applied when the dangling filter was
present, but it should be applied when the dangling filter is NOT
present.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-31 14:58:02 +01:00
8e2943c6c5 image/tree: Sort image tree by name instead of creation date
Sort images alphabetically by their repository tags rather than by
creation date.

When an image has multiple tags, they are sorted internally and the
first tag is used as the representative for sorting the image in the
list. Untagged images are placed at the end.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-31 11:30:59 +01:00
513ee76bac Merge pull request #6593 from thaJeztah/bump_modules
vendor: github.com/moby/moby/api master, moby/client master
2025-10-30 22:45:41 -05:00
8767904ae8 vendor: github.com/moby/moby/api master, moby/client master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-31 03:25:19 +01:00
b8b4f54a89 fix typo in TODO comment
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-31 03:15:47 +01:00
03fb1df2dd Merge pull request #6577 from docker/dependabot/github_actions/actions/upload-artifact-5
build(deps): bump actions/upload-artifact from 4 to 5
2025-10-31 03:02:22 +01:00
58e3de8781 Merge pull request #6594 from thaJeztah/dont_push_me
cli/trust: use local definition for PushResult Aux message
2025-10-31 02:13:01 +01:00
65496c5557 cli/trust: use local definition for PushResult Aux message
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-30 20:22:25 +01:00
3434869388 Merge pull request #6591 from vvoland/update-docker
vendor: github.com/moby/moby master
2025-10-30 00:32:57 +01:00
9dde80abd8 Merge pull request #6574 from vvoland/img-dangling
image/list: Hide untagged images without `--all`
2025-10-30 00:24:29 +01:00
e636a2a069 cli/container_rename: Move to API validation
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-30 00:20:47 +01:00
af255accaa vendor: github.com/moby/moby master
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-30 00:02:41 +01:00
fcac1d5b2a Merge pull request #6586 from thaJeztah/bump_modules
vendor: github.com/moby/moby/api, moby/moby/client master
2025-10-29 23:53:04 +01:00
053aa376ea vendor: github.com/moby/moby/api, moby/moby/client master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-29 23:17:29 +01:00
606f1c65d0 Merge pull request #6584 from thaJeztah/bump_modules
vendor: github.com/moby/moby/api, moby/moby/client master
2025-10-28 19:26:27 +01:00
83319f09f7 cli/command/container: use per-stats OSType if present
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-28 16:27:00 +01:00
6ed16a2cc1 vendor: github.com/moby/moby/api, moby/moby/client master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-28 16:26:53 +01:00
10281ff5a5 Merge pull request #6583 from thaJeztah/stats_cleanups
cli/command/container: cleanup stats code
2025-10-28 15:17:20 +01:00
5007c96b0d cli/command/container: collect(): split windows/unix branches
Use separate branches to handle windows/unix results to reduce intermediate
variables, and make it more transparent what's set for each platform.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-28 15:08:05 +01:00
c467ebafd8 cli/command/container: calculateCPUPercentWindows minor cleanup
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-28 15:08:05 +01:00
689152a804 cli/command/container: calculateCPUPercentUnix: simplify
Pass the whole CPUStats struct instead of deconstructing it to separate
variables.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-28 15:07:58 +01:00
e809e65ba6 Merge pull request #6582 from thaJeztah/fix_stats_cancellation
cli/command/container: RunStats: simplify, and fix context-cancellation
2025-10-28 14:12:16 +01:00
e01ce69ff9 cli/command/container: collect: handle context-cancellation
construct the decoder inside the go-routine, including closing the body,
and add handling for context-cancellation.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-28 13:29:43 +01:00
292001a451 cli/command/container: RunStats: early return for non-streaming
We should consider splitting this out to a separate function, but
start with just an early return before we hit the timer-loop.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-28 12:36:14 +01:00
0b1c7bc0f1 cli/command/container: RunStats: small tweaks on closeChan
Some suggestions from ChatGPT to prevent deadlocks.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-28 12:36:10 +01:00
d309027d58 cli/command/container: RunStats: gracefully handle io.EOF
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-28 12:08:55 +01:00
15b422b317 cli/command/container: RunStats: handle context-cancellation
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-28 12:08:54 +01:00
832fc66ca7 cli/command/container: RunStats: simplify stats loop
Use a single select for the ticker and the closeChan; use early returns.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-28 12:08:54 +01:00
dad1d367c8 cli/command/container: move debug logs to call-site
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-28 12:08:54 +01:00
e50c94f21a Merge pull request #6581 from thaJeztah/stats_ostype
cli/command/container: don't depend on result.OSType
2025-10-27 21:43:53 -05:00
93cd6793b3 Merge pull request #6580 from thaJeztah/no_json
cli/command/image: remove uses of JSON field
2025-10-27 21:42:29 -05:00
f594a7f09b cli/command/image: remove uses of JSON field
The JSON field was added in [moby@9fd2c0f], to address [moby#19177], which
reported an incompatibility with Classic (V1) Swarm, which produced a non-
standard response;

> Make docker load to output json when the response content type is json
> Swarm hijacks the response from docker load and returns JSON rather
> than plain text like the Engine does. This makes the API library to return
> information to figure that out.

A later change in [moby@96d7db6] added additional logic to make sure the
correct content-type was returned, depending on whether the `quiet` option
was set (which produced a non-JSON response). This caused inconsistency in
the API response, and [moby@2f27632] changed the endpoint to always produce
JSON (only skipping the "progress" output if `quiet` was set).

This means that the "load" endpoint ([`imageRouter.postImagesLoad`]) now
unconditionally returns JSON, making the `JSON` field fully redundant.

This patch removes the use of the JSON field, as it's redundant, and the way it handles
the content-type is incorrect because it would not handle correct, but different
formatted response-headers (`application/json; charset=utf-8`), which could
result in malformed output on the client.

[moby@9fd2c0f]: 9fd2c0feb0
[moby#19177]: https://github.com/moby/moby/issues/19177
[moby@96d7db6]: 96d7db665b
[moby@2f27632]: 2f27632cde
[`imageRouter.postImagesLoad`]: 7b9d2ef6e5/api/server/router/image/image_routes.go (L248-L255)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-27 21:37:36 +01:00
4b498addce cli/command/container: don't depend on result.OSType
This field is set for the request as a whole, so can be obtained
from the server-info instead. Docker v29 will provide per-stats
information.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-27 21:35:25 +01:00
715467c9d8 Merge pull request #6569 from jsternberg/otel-sdk-lock-version
telemetry: lock the semconv version of the otel sdk
2025-10-27 21:17:09 +01:00
dafbfc2f7e Merge pull request #6576 from robmry/bump_modules_again
vendor: github.com/moby/moby/api, moby/moby/client master
2025-10-27 20:35:49 +01:00
4a608069a7 vendor: github.com/moby/moby/api, moby/moby/client master
Signed-off-by: Rob Murray <rob.murray@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-27 20:26:27 +01:00
153f7f10c9 telemetry: lock the semconv version of the otel sdk
This change prevents changes to the otel version from affecting the otel
sdk version. This is done by copying the telemetry sdk implementation
locally and using our own choice for semconv from within that.

This prevents a schema conflict from happening since the otel version of
the sdk gets implicitly updated whenever the semconv changes while we
have to manually change ours. Now, we manually change both and they're
locked to each other.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-27 11:44:59 +01:00
aef2ef8c77 build(deps): bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 08:30:59 +00:00
965a0e3518 Merge pull request #6575 from thaJeztah/bump_modules
vendor: github.com/moby/moby/api, moby/moby/client master
2025-10-26 17:40:19 +01:00
4afbd6146b implement some ad-hoc mocks for responses
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-25 01:36:38 +02:00
056e314645 vendor: github.com/moby/moby/api, moby/moby/client master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-25 00:48:49 +02:00
64805c2959 image/list: Respect dangling filter when not using --all
Otherwise `docker images --filter dangling=true` won't work without
`--all`.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-24 20:46:41 +02:00
e9a941001c image/list: Hide untagged images without --all
The `--tree` implementation already does this.

Make the behavior consistent for the legacy image list implementation.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-24 20:46:41 +02:00
f74cd147bb Merge pull request #6571 from thaJeztah/bump_modules
vendor: github.com/moby/moby/api, moby/moby/client master
2025-10-24 16:43:53 +02:00
4f7c07cfc2 update local code for updated modules
Some tests had to be skipped as there's some issues to address, and
some of the result-types cannot be mocked / stubbed.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-24 10:28:54 +02:00
aeb78091a0 vendor: github.com/moby/moby/api, moby/moby/client master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-24 10:28:26 +02:00
7b4cde6967 Merge pull request #6567 from thaJeztah/bump_modules
vendor: github.com/moby/moby/api, moby/moby/client master
2025-10-20 16:52:25 +02:00
563f5fe335 vendor: github.com/moby/moby/api, moby/moby/client master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-20 12:30:05 +02:00
171a9b70b2 Merge pull request #6564 from vvoland/container-go123
cli/command/container: add go1.23 build constraint for range-over-func
2025-10-14 15:00:57 +02:00
5ba4c17d78 cli/command/container: Simplify with slices.Contains
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-14 14:47:59 +02:00
d252afa6b0 cli/command/container: add go1.23 build constraint for range-over-func
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-14 14:43:54 +02:00
bc2aa4eed1 Merge pull request #6563 from thaJeztah/bump_modules
Some checks failed
build / prepare (push) Has been cancelled
build / build (push) Has been cancelled
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, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 25, 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, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 25, 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-14) (push) Has been cancelled
test / host (macos-15) (push) Has been cancelled
test / host (macos-15-intel) (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/moby/moby/api v1.52.0-beta.2, moby/client v0.1.0-beta.2
2025-10-14 11:35:31 +02:00
24e95b8682 vendor: github.com/moby/moby/api v1.52.0-beta.2, moby/client v0.1.0-beta.2
full diff:

- https://github.com/moby/moby/compare/0769fe708773...api/v1.52.0-beta.2
- https://github.com/moby/moby/compare/0769fe708773...client/v0.1.0-beta.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-14 10:43:33 +02:00
c844f92b58 Merge pull request #6562 from vvoland/update-go
update to go1.25.3
2025-10-14 10:28:33 +02:00
07a79d68a6 Merge pull request #6561 from thaJeztah/deprecate_builder_utils
cli/command/image/build: deprecate `DefaultDockerfileName`, `DetectArchiveReader`, `WriteTempDockerfile`, `ResolveAndValidateContextPath`
2025-10-14 10:27:52 +02:00
2bcf047f90 update to go1.25.3
This release addresses breakage caused by a security patch included in
Go 1.25.2 and 1.24.8, which enforced overly restrictive validation on
the parsing of X.509 certificates. We've removed those restrictions
while maintaining the security fix that the initial release addressed.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-14 09:11:31 +02:00
0f2f9e9c41 cli/command/image/build: deprecate ResolveAndValidateContextPath util
This utility was used internally and will be removed in the next release.
Use `DetectContextType` to detect the context-type, and use `GetContextFromLocalDir`,
`GetContextFromLocalDir`, `GetContextFromGitURL`, or `GetContextFromURL`
instead.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 15:42:03 +02:00
6e1ff0bec1 cli/command/image/build: deprecate WriteTempDockerfile util
It was only used internal in the package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 15:29:08 +02:00
c52fa073cd cli/command/image/build: deprecate DetectArchiveReader util
It was only used internal in the package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 15:28:55 +02:00
f24bb4bc76 cli/command/image/build: deprecate DefaultDockerfileName const
It was only used internal in the package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 15:20:32 +02:00
c599e9064e Merge pull request #6556 from thaJeztah/update_go1.25
update to go1.25.2
2025-10-13 12:52:23 +02:00
68ec5bfe20 Merge pull request #6559 from thaJeztah/fix_filteropts_default
opts: FilterOpt: show empty string if no values are set
2025-10-13 12:31:45 +02:00
130a3f5f87 update to go1.25.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 12:12:25 +02:00
e6d150be16 opts: FilterOpt: show empty string if no values are set
follow-up to f81816ef88

We could decide to not initialize a default, and do this only when
setting a value, but this may require more changes, so doing this
as a starting-point.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 12:09:26 +02:00
ecea0c01b3 Merge pull request #6557 from thaJeztah/bump_golangci_lint
Dockerfile: update golangci-lint to v2.5.0 (for go1.25)
2025-10-13 12:02:34 +02:00
2027349052 Merge pull request #6555 from thaJeztah/remove_ResolveDefaultContext
cli/command: remove deprecated ResolveDefaultContext
2025-10-13 12:02:01 +02:00
bb31a8006e Merge pull request #6554 from thaJeztah/bump_goversioninfo
Dockerfile: bump github.com/josephspurrier/goversioninfo to v1.5.0
2025-10-13 12:00:35 +02:00
8ecdfed2af Merge pull request #6552 from thaJeztah/update_gotestsum
Dockerfile: bump gotest.tools/gotestsum v1.13.0
2025-10-13 12:00:02 +02:00
714c82a014 Merge pull request #6558 from thaJeztah/opts_deprecate_delete
opts: deprecate ListOpts.Delete()
2025-10-13 11:59:30 +02:00
aec24ba92c Merge pull request #6553 from thaJeztah/update_utils
Dockerfile: update buildx to v0.29.1, compose v2.40.0
2025-10-13 11:52:47 +02:00
193db8ec41 opts: deprecate ListOpts.Delete()
This method was added as part of a refactor in [moby@1ba1138], at which
time it was used to delete original values for "--host" and "--volume"
after normalizing. This beccame redundant in [moby@6200002], which added
specialized options that used a validate function, which both validated
and normalized inputs.

It's no longer used, so let's mark it deprecated so that we can remove it.

[moby@1ba1138]: 1ba11384bf
[moby@6200002]: 6200002669

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 11:47:39 +02:00
5ad9fbdef7 Dockerfile: update golangci-lint to v2.5.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 10:16:14 +02:00
7abc65bc06 cli/command: remove deprecated ResolveDefaultContext
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-11 00:40:22 +02:00
c2817e2d59 Dockerfile: bump github.com/josephspurrier/goversioninfo to v1.5.0
- Upgrade to Go 1.18 minimum version and remove ioutil references
- Move from Travis to GitHub Actions for CI
- Support multiple icons
- Add options to parse version string

full diff: https://github.com/josephspurrier/goversioninfo/compare/v1.4.1...v1.5.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-11 00:11:08 +02:00
f68d8f1f24 scripts/build/mkversioninfo: use permalink in comment
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-11 00:03:50 +02:00
830e1d60ab Dockerfile: update compose to v2.40.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-10 23:59:38 +02:00
9def7748a5 Dockerfile: update buildx to v0.29.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-10 23:58:35 +02:00
f8b1b8d165 Dockerfile: bump gotest.tools/gotestsum v1.13.0
full diff: https://github.com/gotestyourself/gotestsum/compare/v1.12.3...v1.13.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-10 23:55:52 +02:00
01febbc3bb Merge pull request #6551 from thaJeztah/remove_legacy_api_versions
remove API-version compatibility for API < v1.44
2025-10-10 22:53:01 +02:00
59d228cd98 Merge pull request #6550 from thaJeztah/deprecate_builder_utils
cli/command/image/build: deprecate IsArchive utility
2025-10-10 22:46:27 +02:00
5ad91456c7 docs: update some versions in examples
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-10 21:23:30 +02:00
4c73cefc15 cli/command/container, image: remove addPlatformFlag utility
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-10 21:23:30 +02:00
b8f2b7c678 cli/command/service: remove AppendServiceStatus (API <v1.41)
This function was added in 7405ac5c2d as
a fallback for API < v1.41, which did not include the service status
in the response. Current API versions return this information, so there's
no need to fetch it manually.

It was not gated by API version for some tests (which didn't set API
version), but should not be needed for non-test situations.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-10 21:23:29 +02:00
f046bd371a cli/command/container: rm use of deprecated MacAddress field
This field is no longer in use since API v1.44.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-10 21:23:29 +02:00
d36f16e224 remove API-version compatibility for API < v1.44
Support for API versions < v1.44 was removed in the client in [moby@96b29f5]
and [moby@7652f38], so we can remove fallback-code from the CLI as well,
as it won't be able to use those versions.

[moby@96b29f5]: 96b29f5a1f
[moby@7652f38]: 7652f38c28

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-10 21:23:25 +02:00
a3e954551d Merge pull request #6545 from thaJeztah/bump_engine_take2
vendor: github.com/moby/moby/api, client 0769fe708773 (master)
2025-10-10 19:45:23 +02:00
f81816ef88 vendor: github.com/moby/moby/api, client 0769fe708773 (master)
full diff: 4ca8aedf92...0769fe7087

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-10 19:35:00 +02:00
9e646f6d92 cli/command/image: runBuild: inline vars and minor cleanups
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-10 17:25:04 +02:00
64be664e85 cli/command/image/build: deprecate IsArchive utility
It was only used internally.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-10 17:23:57 +02:00
2c539a6530 cli/command/image/build: fix linting, add sub-tests
- fix minor linting issues (unhandled errors)
- rename vars to prevent shadowing
- use sub-tests for tests that already prepared for it

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-10 17:21:57 +02:00
6ddff81bee Merge pull request #6548 from thaJeztah/improve_TestRemove
e2e/stack: don't run stack deploy "detached"
2025-10-09 14:57:55 -05:00
85ac71a3fe e2e/stack: don't run stack deploy "detached"
Run stack deploy in "attached" mode, so that progress and errors
can be printed on failure.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-09 17:19:41 +02:00
41432adc36 Merge pull request #6547 from thaJeztah/deprecate_ValidateMACAddress
opts: deprecate ValidateMACAddress
2025-10-09 09:13:34 -05:00
17d6a92954 opts: deprecate ValidateMACAddress
It was a wrapper around net.ParseMAC from stdlib, so users should
use that directly.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-09 13:46:33 +02:00
94788a3b63 Merge pull request #6546 from thaJeztah/test_improvements
assorted test-improvements
2025-10-09 12:46:00 +02:00
af34b8471a cli/command/network: TestNetworkCreateWithFlags: fix unhandled errs
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-09 12:29:56 +02:00
c8014ec509 cli/command/network: TestNetworkCreateErrors: use sub-tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-09 12:22:08 +02:00
413ee120de cli/command/container: TestParseWithExpose: use sub-tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-09 12:13:25 +02:00
7923f440ed cli/command/container: update todo comment
Provide some context to the TODO

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-09 11:51:07 +02:00
b1ea4fe9d9 Merge pull request #6543 from docker/dependabot/github_actions/github/codeql-action-4
build(deps): bump github/codeql-action from 3 to 4
2025-10-08 16:22:13 +02:00
5483b10e94 build(deps): bump github/codeql-action from 3 to 4
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-08 08:04:23 +00:00
9f941a49c1 Merge pull request #6540 from vvoland/update-go
update to go1.24.8
2025-10-07 19:47:18 -07:00
e598ea0176 update to go1.24.8
This minor release includes 10 security fixes following the security policy:

- net/mail: excessive CPU consumption in ParseAddress

    The ParseAddress function constructed domain-literal address components through repeated string concatenation. When parsing large domain-literal components, this could cause excessive CPU consumption.

    Thanks to Philippe Antoine (Catena cyber) for reporting this issue.

    This is CVE-2025-61725 and Go issue https://go.dev/issue/75680.

- crypto/x509: quadratic complexity when checking name constraints

    Due to the design of the name constraint checking algorithm, the processing time
    of some inputs scales non-linearly with respect to the size of the certificate.

    This affects programs which validate arbitrary certificate chains.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-58187 and Go issue https://go.dev/issue/75681.

- crypto/tls: ALPN negotiation errors can contain arbitrary text

    The crypto/tls conn.Handshake method returns an error on the server-side when
    ALPN negotation fails which can contain arbitrary attacker controlled
    information provided by the client-side of the connection which is not escaped.

    This affects programs which log these errors without any additional form of
    sanitization, and may allow injection of attacker controlled information into
    logs.

    Thanks to National Cyber Security Centre Finland for reporting this issue.

    This is CVE-2025-58189 and Go issue https://go.dev/issue/75652.

- encoding/pem: quadratic complexity when parsing some invalid inputs

    Due to the design of the PEM parsing function, the processing time for some
    inputs scales non-linearly with respect to the size of the input.

    This affects programs which parse untrusted PEM inputs.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-61723 and Go issue https://go.dev/issue/75676.

- net/url: insufficient validation of bracketed IPv6 hostnames

    The Parse function permitted values other than IPv6 addresses to be included in square brackets within the host component of a URL. RFC 3986 permits IPv6 addresses to be included within the host component, enclosed within square brackets. For example: "http://[::1]/". IPv4 addresses and hostnames must not appear within square brackets. Parse did not enforce this requirement.

    Thanks to Enze Wang, Jingcheng Yang and Zehui Miao of Tsinghua University for reporting this issue.

    This is CVE-2025-47912 and Go issue https://go.dev/issue/75678.

- encoding/asn1: pre-allocating memory when parsing DER payload can cause memory exhaustion

    When parsing DER payloads, memories were being allocated prior to fully validating the payloads.
    This permits an attacker to craft a big empty DER payload to cause memory exhaustion in functions such as asn1.Unmarshal, x509.ParseCertificateRequest, and ocsp.ParseResponse.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-58185 and Go issue https://go.dev/issue/75671.

- net/http: lack of limit when parsing cookies can cause memory exhaustion

    Despite HTTP headers having a default limit of 1 MB, the number of cookies that can be parsed did not have a limit.
    By sending a lot of very small cookies such as "a=;", an attacker can make an HTTP server allocate a large amount of structs, causing large memory consumption.

    net/http now limits the number of cookies accepted to 3000, which can be adjusted using the httpcookiemaxnum GODEBUG option.

    Thanks to jub0bs for reporting this issue.

    This is CVE-2025-58186 and Go issue https://go.dev/issue/75672.

- crypto/x509: panic when validating certificates with DSA public keys

    Validating certificate chains which contain DSA public keys can cause programs
    to panic, due to a interface cast that assumes they implement the Equal method.

    This affects programs which validate arbitrary certificate chains.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-58188 and Go issue https://go.dev/issue/75675.

- archive/tar: unbounded allocation when parsing GNU sparse map

    tar.Reader did not set a maximum size on the number of sparse region data blocks in GNU tar pax 1.0 sparse files. A maliciously-crafted archive containing a large number of sparse regions could cause a Reader to read an unbounded amount of data from the archive into memory. When reading from a compressed source, a small compressed input could result in large allocations.

    Thanks to Harshit Gupta (Mr HAX) - https://www.linkedin.com/in/iam-harshit-gupta/ for reporting this issue.

    This is CVE-2025-58183 and Go issue https://go.dev/issue/75677.

- net/textproto: excessive CPU consumption in Reader.ReadResponse

    The Reader.ReadResponse function constructed a response string through
    repeated string concatenation of lines. When the number of lines in a response is large,
    this could cause excessive CPU consumption.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-61724 and Go issue https://go.dev/issue/75716.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-07 21:46:51 +02:00
d17643a675 Merge pull request #6537 from thaJeztah/no_nw_shadow
rename some vars to prevent shadowing imports
2025-10-07 06:31:30 -07:00
3754fe3c8a rename some vars to prevent shadowing imports
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-06 22:49:02 +02:00
30ec4c09b3 Merge pull request #6536 from thaJeztah/cleanups
cli/command/container: inline some variables
2025-10-06 13:21:31 -07:00
6d351158cc Merge pull request #6535 from thaJeztah/remove_localhostDNSWarning
cli/command/container: remove localhostDNSWarning
2025-10-06 13:10:37 -07:00
6222292566 cli/command/container: inline some variables
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-06 21:26:17 +02:00
c06c08531a cli/command/container: remove localhostDNSWarning
This warning is better handled by the daemon, where applicable, as
the client does not have all information available to determine
if using a localhost / loopback-address for the DNS is possible.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-06 21:23:50 +02:00
74e3520724 Merge pull request #6530 from thaJeztah/bump_macos
gha: add macOS 15, remove macOS 13 (deprecated)
2025-10-03 14:04:10 +00:00
886e041790 Merge pull request #6532 from thaJeztah/bump_creds_helper_0.9.4
vendor: github.com/docker/docker-credential-helpers v0.9.4
2025-10-03 14:02:59 +00:00
9dea52c193 Merge pull request #6527 from thaJeztah/no_WithInitializeClient
cli/command: don't use WithInitializeClient in test
2025-10-03 14:02:37 +00:00
780c427550 Merge pull request #6526 from thaJeztah/no_plugin_load
cmd/docker: setFlagErrorFunc: don't load plugins for invalid flags
2025-10-03 14:02:16 +00:00
ca6f899a58 Merge pull request #6525 from thaJeztah/rm_client_side_autorm
remove support for AutoRemove (`--rm`) on API < 1.30
2025-10-03 11:58:03 +02:00
395152ce88 vendor: github.com/docker/docker-credential-helpers v0.9.4
full diff: https://github.com/docker/docker-credential-helpers/compare/v0.9.3...v0.9.4

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-02 22:25:45 +02:00
f5a7a3c72e Merge pull request #6528 from thaJeztah/deprecate_ResolveDefaultContext
cli/command: deprecate ResolveDefaultContext
2025-10-01 13:25:04 +00:00
394ab41696 Merge pull request #6524 from thaJeztah/rm_deprecated_virtualsize
remove VirtualSize formatting options and output
2025-10-01 12:09:25 +02:00
91d8c0bf62 gha: add macOS 15, remove macOS 13 (deprecated)
The macOS 13 runners are deprecated and will be removed on December 4th,
with brownouts in November;
https://github.blog/changelog/2025-09-19-github-actions-macos-13-runner-image-is-closing-down/

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 11:59:56 +02:00
311a97a210 cli/command: deprecate ResolveDefaultContext
The ResolveDefaultContext function was exported in [cli@f820766] to allow
(unit) testing, but did not document that it was only exported for this
purpose. The only external use of this function is in buildx, which uses
it in a unit test that can be implemented without this function.

This patch deprecates the function so that we can remove it.

[cli@f820766]: f820766f6a

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 11:47:24 +02:00
b375006c3e cli/command: don't use WithInitializeClient in test
It's just a wrapper around WithAPIClient, and not needed for this
test, which validates that "Initialize" properly creates the context
store, even if a client was already set;
3b26cfce8b

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 10:12:11 +02:00
b0201c8531 cmd/docker: hideUnsupportedFeatures: remove unused error return
```
70.72 cmd/docker/docker.go:560:74: hideUnsupportedFeatures - result 0 (error) is always nil (unparam)
70.72 func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error {
70.72                                                                          ^
70.72 1 issues:
```

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 01:57:59 +02:00
2b4fd0d750 cmd/docker: setFlagErrorFunc: don't load plugins for invalid flags
On Docker CLI versions before v28.0.0, using an unknown flag would print
the usage output, showing all available top-level flags and commands;

    docker --badopt
    unknown flag: --badopt
    See 'docker --help'.

    Usage:  docker [OPTIONS] COMMAND

    A self-sufficient runtime for containers

    Options:
          --config string      Location of client config files (default "/root/.docker")
    ...

This output did not include plugin-commands, making the usage output
incomplete. That issue was fixed in [cli@40a6cf7], which loaded all
available cli-plugins, so that a stub was created for printing the
plugin commands in the usage output. Similarly, [cli@79a75da] added
code to hide experimental commands and commands not supported by the
daemon.

However, since 28.0.0 (commit [cli@f28fc7f]), the usage output was
removed for this error, so loading plugins is no longer needed;

    docker --badopt
    unknown flag: --badopt

    Usage:  docker [OPTIONS] COMMAND [ARG...]

    Run 'docker --help' for more information

This patch removes the code added in [cli@40a6cf7] and [cli@79a75da].

With this patch, the output is still the same;

    docker --unknown-flag buildx ls --no-such
    unknown flag: --unknown-flag

    Usage:  docker [OPTIONS] COMMAND [ARG...]

    Run 'docker --help' for more information

This function only handles flags defined by the CLI itself; invalid
flags for plugins are handled by the plugin itself, so are not
impacted;

    docker buildx ls --no-such
    unknown flag: --no-such

    Usage:  docker buildx ls

    Run 'docker buildx ls --help' for more information

[cli@f28fc7f]: f28fc7f82f
[cli@40a6cf7]: 40a6cf7c47
[cli@79a75da]: 79a75da0fd

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 01:15:53 +02:00
63c5254201 remove support for AutoRemove (--rm) on API < 1.30
Support for daemon-side auto-remove was added in API v1.25; on older
versions of the daemon, the client was responsible for removing the
container after it exited (see [moby@6dd8e10])

On API versions < 1.30, it used the events API for this purpose, and
would wait for a "die", "detach" or "detroy" events to know the container
exited, and could be removed or (when attached, but without a TTY) to
get the container's exit-status. (see [cli@38591f2]).

API version 1.24 (docker 1.12) is 9 Years old (July 29, 2016), and API
1.30 (docker 17.06) is 8 Years old (Jun 20, 2017), and long EOL. While
technically, a CLI could negotiate API 1.30 or older, this would only
be in cases where either API version negotiation failed, or the version
was explicitly overridden through `DOCKER_API_VERSION` for testing.

Either of those cases would be rare, and not worth the technical complexity
to support. This patch removes support for AutoRemove on API < 1.30.

[moby@6dd8e10]: 6dd8e10d6e
[cli@38591f2]: 38591f20d0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-30 18:16:57 +02:00
1b90a53be2 remove deprecated VirtualSize formatting options and output
The `VirtualSize` field was deprecated in [moby@1261fe6], and omitted / removed
in API v1.44 in [moby@913b0f5], and the corresponding formatting placeholder
was deprecated in [cli@f02301a].

This patch removes the formatting function, which also removes it from the
`docker image ls --format=json` output.

[moby@1261fe6]: 1261fe69a3
[moby@913b0f5]: 913b0f51ca
[cli@f02301a]: f02301ab5d

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-30 16:31:57 +02:00
c361deb85d docs, man: image ls: remove VirtualSize from examples
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-30 16:26:06 +02:00
adfcb88896 man: inspect: update some inspect examples
remove various deprecated fields from the example

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-30 16:23:19 +02:00
9c79528489 Merge pull request #6523 from thaJeztah/rm_deprecated
cli/manifest/store: remove deprecated IsNotFound
2025-09-30 05:36:34 -07:00
9874437110 Merge pull request #6521 from thaJeztah/bump_moby
vendor: github.com/moby/moby/api, moby/client master
2025-09-30 05:35:30 -07:00
39d9a0cd51 cli/manifest/store: remove deprecated IsNotFound
This was deprecated in f3fb7728c7, which
is part of 28.5.0, and no longer used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-30 12:59:02 +02:00
9d97363f8f vendor: github.com/moby/moby/api, moby/client master
full diff: 9a97f59e6e...4ca8aedf92

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-30 10:43:02 +02:00
21e768adb7 Merge pull request #6520 from thaJeztah/bump_moby
vendor: github.com/moby/moby/api, moby/client master
2025-09-30 09:31:05 +02:00
cdcf267264 vendor: github.com/moby/moby/api, moby/client master
full diff: e98849831f...9a97f59e6e

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-29 18:00:59 -05:00
f8932e916b Merge pull request #6515 from thaJeztah/rm_authconfig_email
cli/config/types: remove deprecated AuthConfig.Email field
2025-09-29 16:00:21 -07:00
e9664b9f34 cli/config/types: remove deprecated AuthConfig.Email field
Relates to [cli@27b2797], which forked this type from the Moby API, and
[cli@aab947d], which fixed the deprecation comment.

This field is no longer used since Docker 1.11 (API version 1.23) through
[moby@aee260d] and [engine-api@9a9e468], and the fix of the deprecation
comment was included in the 28.4.0 release.

This patch removes the field.

[cli@27b2797]: 27b2797f7d
[cli@aab947d]: aab947de8f
[moby@aee260d]: aee260d4eb
[engine-api@9a9e468]: 9a9e468f50

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-29 17:50:54 -05:00
a61ecaf3c7 Merge pull request #6516 from thaJeztah/authconfig_no_direct_cast
cli/command: explicitly map AuthConfig fields instead of a direct cast
2025-09-29 15:49:22 -07:00
7e8b893952 Merge pull request #6517 from thaJeztah/memstore_notfounderr
cli/config/memorystore: remove unused IsErrValueNotFound
2025-09-29 15:13:24 -07:00
5a4758f513 Merge pull request #6513 from thaJeztah/manifeststore_notfound
cli/manifest/store: deprecate IsNotFound
2025-09-29 15:11:13 -07:00
1b467f909c Merge pull request #6512 from thaJeztah/less_trust
remove some uses of trust-specific types
2025-09-29 15:02:31 -07:00
3c78ac2aad cli/config/memorystore: remove unused IsErrValueNotFound
This utility was added in 9b83d5bbf9, but
was never used. Remove the utility, and rewrite the error returned to
implement the errdefs.NotFound interface, so that it can be detected
using the errdefs.IsNotFound() utility if needed.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-29 13:05:08 +02:00
9f02d9643d cli/command: explicitly map AuthConfig fields instead of a direct cast
Commit [cli@27b2797] forked the AuthConfig type from the API, and changed
existing code to do a direct cast / convert of the forked type to the API
type. This can cause issues if the API types diverges, such as the removal
of the Email field.

This patch explicitly maps each field to the corresponding API type, but
adds some TODOs, because various code-paths only included a subset of the
fields, which may be intentional for fields that were meant to be handled
on the daemon / registry-client only.

We should evaluate these conversions to make sure these fields should
be sent from the client or not (and possibly even removed from the API
type).

[cli@27b2797]: 27b2797f7d

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-29 12:57:52 +02:00
f3fb7728c7 cli/manifest/store: deprecate IsNotFound
Deprecate the IsNotFound utility in favor of errdefs.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-26 23:33:01 +02:00
0dec83f572 cli/command/image: imagePullPrivileged: move to non-trust file
This function is a wrapper around apiClient.ImagePull and not directly
related to docker content trust; it just happens to also be called
when using content trust (through the trustedPull utility).

Move it together with the `runPull` function to separate it from
trust-related code.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-26 20:06:32 +02:00
e58a6ace45 cli/command/image: imagePullPrivileged: don't use ImageRefAndAuth
This function is a wrapper around apiClient.ImagePull; the use of
trust.ImageRefAndAuth was out of convenience because it's also called
when using content trust (through the trustedPull utility).

Let's pull away the layers to separate it from trust code.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-26 20:06:32 +02:00
a6946d0fbf cli/command/image: notaryClientProvider: don't require arguments
This interface is used in tests to provide a dummy notary client,
but none of the tests require any arguments, so let's remove them.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-26 20:06:26 +02:00
c3317b0a43 cli/trust: Server: accept registry hostname
The IndexInfo was only used to detect if the target was an official
image, which we can deduct from the hostname. Adding some normalizing
just in case (but we should only get "docker.io" here).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-26 18:51:32 +02:00
75f3c08257 Merge pull request #6505 from thaJeztah/bump_runewidth
vendor: github.com/mattn/go-runewidth v0.0.17
2025-09-26 11:31:18 +02:00
aaed38f5d5 Merge pull request #6507 from thaJeztah/split_trust_auth
cli/command/image: pushTrustedReference: internalize constructing indexInfo
2025-09-26 11:24:31 +02:00
fdf9b43ca8 Merge pull request #6506 from thaJeztah/bump_mapstructure
vendor: github.com/go-viper/mapstructure/v2 v2.4.0
2025-09-26 09:22:38 +00:00
95735d1631 Merge pull request #6504 from thaJeztah/bump_protobuf
vendor: google.golang.org/protobuf v1.36.9
2025-09-26 09:22:29 +00:00
013c41c602 Merge pull request #6508 from thaJeztah/dct_retiring
trust: print deprecation warning when using hub Notary server
2025-09-26 09:16:45 +00:00
43b03ef2c5 trust: print deprecation warning when using hub Notary server
Docker Hub's Notary service is being retired, and now produces
failures in most cases. Add a warning when attempting to use
it, pending full removal of trust;
https://www.docker.com/blog/retiring-docker-content-trust/

With this PR:

    DOCKER_CONTENT_TRUST=1 docker pull -q hello-world
    WARNING: Docker is retiring DCT for Docker Official Images (DOI).
             For details, refer to https://docs.docker.com/go/dct-deprecation/

    could not validate the path to a trusted root: unable to retrieve valid leaf certificates

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-26 11:09:07 +02:00
9a6313ed3b cli/command/image: pushTrustedReference: internalize constructing indexInfo
All information needed can be deducted from the image reference, which
is used to create a indexInfo, repoInfo, and to resolve auth-config.

In some situations this may result in resolving the auth-config twice
after it already was resolved to an encoded auth-config.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-26 10:09:12 +02:00
20e5dc9469 vendor: github.com/go-viper/mapstructure/v2 v2.4.0
full diff: https://github.com/go-viper/mapstructure/compare/v2.2.1...v2.4.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 19:47:12 +02:00
4287f1d887 vendor: github.com/mattn/go-runewidth v0.0.17
full diff: https://github.com/mattn/go-runewidth/compare/v0.0.16...v0.0.17

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 19:44:27 +02:00
746d7cb39e vendor: google.golang.org/protobuf v1.36.9
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 19:41:34 +02:00
6855d70c52 Merge pull request #6503 from thaJeztah/rm_apply
cli/command: remove deprecated DockerCli.Apply
2025-09-25 18:43:28 +02:00
1a80524834 Merge pull request #6502 from thaJeztah/rm_deprecated
cli/command: remove deprecated methods and options
2025-09-25 18:42:48 +02:00
d719b416ab Merge pull request #6500 from thaJeztah/bump_go_events
vendor: github.com/docker/go-events v0.0.0-20250808211157-605354379745
2025-09-25 18:42:17 +02:00
008b2df526 Merge pull request #6499 from thaJeztah/bump_swarmkit
vendor: github.com/moby/swarmkit/v2 v2.1.0
2025-09-25 18:41:50 +02:00
96732f858a Merge pull request #6498 from thaJeztah/bump_jose
vendor: github.com/go-jose/go-jose/v4 v4.1.2
2025-09-25 18:41:17 +02:00
1b085a2b63 cli/command: remove deprecated DockerCli.Apply
The Apply method was added when CLI options for constructing the CLI were
rewritten into functional options in [cli@7f207f3]. There was no mention
in the pull request of this method specifically, and this may have been
related to work being done elsewhere on compose-on-kubernetes or the
compose-cli plugin that may have needed options to modify the CLI config
after it was already initialized.

The CLI itself no longer depends on this method since [cli@133279f], and
there are no known external users. It was deprecated in [cli@24bfedf],
which is included in the 28.5.0 release, so we can remove it for 29.0.

[cli@7f207f3]: 7f207f3f95
[cli@133279f]: 133279fb0d
[cli@24bfedf]: 24bfedf3f8

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 18:16:20 +02:00
b0cb6406ff cli/command: remove Apply from Cli interface
The Apply command was deprecated in 24bfedf3f8,
and has no known external users, but we didn't remove it from the interface.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 18:09:10 +02:00
782deffe83 cli/command: remove deprecated DockerCli.DefaultVersion
This function was used internally, but is no longer used.

This method was deprecated in 0270b2d6f7,
which was included in the 28.5.0 release, and has no known external users,
so removing it for 29.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 17:51:09 +02:00
e25843bfb6 cli/command: remove deprecated WithContentTrustFromEnv, WithContentTrust
These options were used internally as defaults for the constructor and
only impact commands implemented in the CLI itself.

They were deprecated in 40cdfc0d81, which
was included in the 28.5.0 release, so removing it for 29.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 17:47:51 +02:00
592afa8c73 cli/command: remove deprecated DockerCli.ContentTrustEnabled
This function was used internally, but is no longer used. Users should check
the value of the `DOCKER_CONTENT_TRUST` environment variable instead.

This method was deprecated in 11d40488dd,
which was included in the 28.5.0 release, and has no known external users,
so removing it for 29.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 17:42:36 +02:00
6d1c037640 vendor: github.com/docker/go-events v0.0.0-20250808211157-605354379745
full diff: c867878c5e...6053543797

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 15:13:31 +02:00
9cba658e9a vendor: github.com/moby/swarmkit/v2 v2.1.0
full diff: https://github.com/moby/swarmkit/compare/v2.0.0...v2.1.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 15:07:05 +02:00
57314d42a2 vendor: github.com/go-jose/go-jose/v4 v4.1.2
full diff: https://github.com/go-jose/go-jose/compare/v4.0.5...v4.1.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 14:58:11 +02:00
e922dbefca vendor: golang.org/x/crypto v0.39.0
no changes in vendored files

full diff: https://github.com/golang/crypto/compare/v0.37.0...v0.39.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 14:58:05 +02:00
286658d7ff vendor: golang.org/x/text v0.26.0
no changes in vendored files

full diff: https://github.com/golang/text/compare/v0.24.0...v0.26.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 14:55:03 +02:00
90959c40bd vendor: golang.org/x/term v0.32.0
full diff: https://github.com/golang/term/compare/v0.31.0...v0.32.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 14:54:18 +02:00
f369c5bcf2 Merge pull request #6496 from thaJeztah/deprecate_apply
cli/command: deprecate DockerCli.Apply
2025-09-25 12:29:19 +00:00
24bfedf3f8 cli/command: deprecate DockerCli.Apply
The Apply method was added when CLI options for constructing the CLI were
rewritten into functional options in [cli@7f207f3]. There was no mention
in the pull request of this method specifically, and this may have been
related to work being done elsewhere on compose-on-kubernetes or the
compose-cli plugin that may have needed options to modify the CLI config
after it was already initialized.

The CLI itself no longer depends on this method since [cli@133279f], and
the only known consumer (docker compose) no longer needs it since [cli@2711800]
and [cli@048e931].

This patch deprecates the method with the intent to remove it in a future
release.

[cli@7f207f3]: 7f207f3f95
[cli@133279f]: 133279fb0d
[cli@2711800]: 2711800430
[cli@048e931]: 048e931b42

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 13:27:28 +02:00
ef3c19a80b Merge pull request #6494 from thaJeztah/deprecate_ContentTrustEnabled
cli/command: deprecate DockerCli.ContentTrustEnabled
2025-09-25 11:24:05 +00:00
11d40488dd cli/command: deprecate DockerCli.ContentTrustEnabled
This function was used internally, but is no longer used. Users should check
the value of the `DOCKER_CONTENT_TRUST` environment variable instead.

There are no known external users of this method, so already removing it
from the Cli interface; this method will be removed in the next release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 12:45:13 +02:00
1bae6aafa8 trust: add internal utility for checking DOCKER_CONTENT_TRUST
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 12:45:08 +02:00
1ace9aec34 cli/command: don't use DCT status for trust stub-flags
This is a follow-up to 7609dde8d0 and
3f5b1bdd32, which removed support for
DCT for build and plugin commands.

As these flags are just stubs, hidden by default and no longer functional,
they don't have to reflect the current state of DCT.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 12:39:28 +02:00
734328eef9 Merge pull request #6492 from thaJeztah/fix_alpine
e2e: update openssh, openssl to work around openssh bug
2025-09-25 11:39:52 +02:00
9c88b315ef Merge pull request #6488 from thaJeztah/deprecate_dct_opts
cli/command: deprecate WithContentTrustFromEnv, WithContentTrust
2025-09-25 11:31:55 +02:00
04bfe7dc78 Merge pull request #6490 from thaJeztah/deprecate_defaultversion
cli/command: deprecate DockerCli.DefaultVersion
2025-09-25 11:31:27 +02:00
b611f288ee e2e: update openssh, openssl to work around openssh bug
relates to https://gitlab.alpinelinux.org/alpine/aports/-/issues/17547

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 10:57:14 +02:00
40cdfc0d81 cli/command: deprecate WithContentTrustFromEnv, WithContentTrust
These options were used internally as defaults for the constructor and
only impact commands implemented in the CLI itself.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 09:57:28 +02:00
0270b2d6f7 cli/command: deprecate DockerCli.DefaultVersion
This function was used internally, but is no longer used. There are
no known users of this method, so already removing it from the Cli
interface.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 09:38:46 +02:00
903e9b3426 Merge pull request #6453 from thaJeztah/rm_deprecated_template
templates: remove deprecated NewParse function
2025-09-24 10:40:45 -07:00
a44144e1db Merge pull request #6479 from thaJeztah/plugin_semverish
cli-plugins/manager: allow schema-versions <= 2.0.0
2025-09-24 19:25:43 +02:00
ed7908e4ed Merge pull request #4574 from milas/cli-user-agent
cli/command: add WithUserAgent option
2025-09-24 19:16:38 +02:00
048e931b42 cli/command: add WithUserAgent option
Add support to the `cli/command` package to accept a custom User
Agent to pass to the underlying client.

This is used as the `UpstreamClient` portion of the `User-Agent`
when the Moby daemon makes requests.

For example, pushing and pulling images with Compose might result
in the registry seeing a `User-Agent` value of:

```
docker/24.0.7 go/go1.20.10 git-commit/311b9ff kernel/6.5.13-linuxkit os/linux arch/arm64 UpstreamClient(docker-cli-plugin-compose/v2.24.0)
```

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 17:43:41 +02:00
ec912e5524 cli-plugins/manager: allow schema-versions <= 2.0.0
The CLI currently hard-codes the schema-version for CLI plugins to
"0.1.0", which doesn't allow us to expand the schema for plugins.

As there's many plugins that we shipped already, we can't break
compatibility until we reach 2.0.0, but we can expand the schema
with non-breaking changes.

This patch makes the validation more permissive to allow new schema
versions <= 2.0.0. Note that existing CLIs will still invalidate
such versions, so we cannot update the version until such CLIs are
no longer expected to be used, but this patch lays the ground-work
to open that option.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 16:17:33 +02:00
8fbb70ae56 Merge pull request #6475 from thaJeztah/cli_plugin_options
cli-plugins/plugin: Run: allow customizing the CLI
2025-09-24 07:15:24 -07:00
f3687d8a8b Merge pull request #5922 from thaJeztah/ignore_broken_symlinks
cli-plugins/manager: ignore broken symlinks
2025-09-24 15:10:23 +02:00
9b2f831452 cli-plugins/manager: ignore broken symlinks
Before this patch, a broken symlink would print a warning;

    docker info > /dev/null
    WARNING: Plugin "/Users/thajeztah/.docker/cli-plugins/docker-feedback" is not valid: failed to fetch metadata: fork/exec /Users/thajeztah/.docker/cli-plugins/docker-feedback: no such file or directory

After this patch, such symlinks are ignored:

    docker info > /dev/null

With debug enabled, we don't ignore the faulty plugin, which will
make the warning shown on docker info;

    mkdir -p ~/.docker/cli-plugins
    ln -s nosuchplugin ~/.docker/cli-plugins/docker-brokenplugin
    docker --debug info
    Client:
     Version:    29.0.0-dev
     Context:    default
     Debug Mode: true
     Plugins:
      buildx: Docker Buildx (Docker Inc.)
        Version:  v0.25.0
        Path:     /usr/libexec/docker/cli-plugins/docker-buildx
    WARNING: Plugin "/Users/thajeztah/.docker/cli-plugins/docker-brokenplugin" is not valid: failed to fetch metadata: fork/exec /Users/thajeztah/.docker/cli-plugins/docker-brokenplugin: no such file or directory

    # ...

We should als consider passing a "seen" map to de-duplicate entries.
Entries can be either a direct symlink or in a symlinked path (for
which we can filepath.EvalSymlinks). We need to benchmark the overhead
of resolving the symlink vs possibly calling the plugin (to get their
metadata) further down the line.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 14:32:37 +02:00
2711800430 cli-plugins/plugin: Run: allow customizing the CLI
Currently, the plugin.Run command constructs the DockerCli using
the default options, assuming plugins run with all the same options
as the CLI itself; to customize the CLI there's a "Apply" option,
but this means mutating the CLI after it's already constructed, which
is not ideal.

This patch adds a variadic ops argument to allow CLI plugins to pass
custom options to use for the CLI, so that there's no need to mutate
its config in most cases.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 14:17:15 +02:00
84520653d8 templates: remove deprecated NewParse function
This was deprecated in 7ab3e7e774 and
no longer used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 14:08:27 +02:00
c8600e1cea Merge pull request #6463 from thaJeztah/remove_oauth_escape_hatch
remove DOCKER_CLI_DISABLE_OAUTH_LOGIN escape hatch
2025-09-24 13:43:42 +02:00
4a4043cdb6 Merge pull request #6462 from thaJeztah/rm_deprecated_registryclient
remove deprecated cli/registry/client package
2025-09-24 13:18:58 +02:00
7cc801d93d Merge pull request #6467 from thaJeztah/no_apply
cli/command: NewDockerCli: don't depend on DockerCli.Apply
2025-09-24 13:17:51 +02:00
7afda4c6c5 Merge pull request #6468 from thaJeztah/cli_plugins_touchup
cli-plugins/plugin: Run: touch-up godoc and minor cleanups
2025-09-24 13:17:38 +02:00
550d40f7bc Merge pull request #6466 from thaJeztah/registry_3
e2e: use registry v3
2025-09-24 13:17:23 +02:00
5710de6d9a Merge pull request #6461 from thaJeztah/bump_xx
Dockerfile: update xx to v1.7.0
2025-09-24 13:17:13 +02:00
93bb8a7a0a Merge pull request #6458 from thaJeztah/bump_engine
vendor: github.com/moby/moby/api, github.com/moby/moby/client master
2025-09-24 13:16:39 +02:00
b1d45285ba Merge pull request #6464 from thaJeztah/remove_deprecated_experimental
cli/config/configfile: remove deprecated ConfigFile.Experimental field
2025-09-24 13:15:54 +02:00
4c802a1548 Merge pull request #6465 from thaJeztah/rm_23_test
gha: update test-matrix: remove docker 23.x, 26.x, add 25.x
2025-09-24 13:15:27 +02:00
635a718209 cli-plugins/plugin: Run: touch-up godoc and minor cleanups
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 12:57:42 +02:00
133279fb0d cli/command: NewDockerCli: don't depend on DockerCli.Apply
The Apply method was added when CLI options for constructing the CLI were
rewritten into functional options in [cli@7f207f3]. There was no mention
in the pull request of this method specifically, and this may have been
related to work being done elsewhere on compose-on-kubernetes or the
compose-cli plugin that may have needed options to modify the CLI config
after it was already initialized.

We should try to remove functions that mutate the CLI configuration after
initialization if possible (and likely remove the `Apply` method); currently
this function is used in docker compose, but as part of a hack that can
probably be avoided.

[cli@7f207f3]: 7f207f3f95

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 12:10:57 +02:00
daa15c3bfa e2e: use registry v3
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 10:28:42 +02:00
83e40c39b4 gha: update test-matrix: remove docker 23.x, 26.x, add 25.x
- Mirantis Container Runtime (MCR) 23.0 reached EOL, and the next LTS
  version of MCR is 25.x
- Docker 26.x reached EOL and is no longer maintained

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 10:12:05 +02:00
71f46056c9 remove DOCKER_CLI_DISABLE_OAUTH_LOGIN escape hatch
This code was added in 846ecf59ff as an
escape hatch in case the new OAuth login flow would cause problems.
We have not received reports where the new flow caused problems, and
searching the internet shows no mentions of the env-var.

This env-var was not documented, so we can remove it.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-23 21:47:30 +02:00
a53e83a3d4 cli/config/configfile: remove deprecated ConfigFile.Experimental field
Configuration options for experimental CLI features were deprecated in
docker 19.03 (3172219932), and enabled by
default since docker 20.10 (977d3ae046).

This field was deprecated in c8f9187157,
which is part of the 28.x release, and is unused. This patch removes
the field.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-23 21:43:14 +02:00
083e5ce872 cli/command/registry: remove deprecated OauthLoginEscapeHatchEnvVar
This const was added in 846ecf59ff, but
only used internally; commit 18cdc25bb4
deprecated the const, which was included in the 28.4 release.

This patch removes the exported const, as it's unused.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-23 21:31:43 +02:00
3cf005ec91 remove deprecated cli/registry/client package
This package was deprecated in 13010ba673,
and only used internally. The deprecation was included in the 28.4 release,
and there's no known external users, so we can remove it for v29.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-23 21:21:31 +02:00
c5cbb3e648 vendor: github.com/moby/moby/api, github.com/moby/moby/client master
full diffs:

- https://github.com/moby/moby/compare/api/v1.52.0-beta.1...e98849831fc4e35bdc09ed31b85f91caa87a0103
- https://github.com/moby/moby/compare/client/v0.1.0-beta.0...e98849831fc4e35bdc09ed31b85f91caa87a0103

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-23 21:11:17 +02:00
e241f53ebc Merge pull request #6460 from thaJeztah/no_pause
deprecate "--pause" flag on docker commit in favor of "--no-pause"
2025-09-23 12:54:15 +02:00
0f08b55bce Dockerfile: update xx to v1.7.0
full diff: https://github.com/tonistiigi/xx/compare/v1.6.1...v1.7.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-23 10:42:15 +02:00
3c244d1099 deprecate "--pause" flag on docker commit in favor of "--no-pause"
Commit [moby@17d870b] (API v1.13, docker v1.1.0) changed the default to pause
containers during commit, keeping the behavior opt-in for older API versions.
This version-gate was removed in [moby@1b1147e] because API versions lower
than v1.23 were no longer supported.

This patch deprecates the `--pause` flag in favor of a `--no-pause` flag to
be more explicit on the default. The old `--pause` flag is marked deprecated
but still functional. Using the deprecated flag will print a warning, and an
error is produced when trying to use both the old and new flag;

    docker commit --pause mycontainer
    Flag --pause has been deprecated, and enabled by default. Use --no-pause to disable pausing during commit.

    docker commit --pause=false mycontainer
    Flag --pause has been deprecated, and enabled by default. Use --no-pause to disable pausing during commit.

    docker commit --pause --no-pause mycontainer
    Flag --pause has been deprecated, use --no-pause instead
    conflicting options: --no-pause and --pause cannot be used together

[moby@17d870b]: 17d870bed5
[moby@1b1147e]: 1b1147e46b

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-22 15:41:22 +02:00
233322637a Merge pull request #6452 from doringeman/image-list-completions
Enable completion for `docker images`
2025-09-19 10:57:16 +02:00
5d8fb335d4 Merge pull request #6455 from thaJeztah/rm_kmem
cli/command/container: fully deprecate --kernel-memory options
2025-09-16 19:40:56 +02:00
f4a433f841 cli/command/container: fully deprecate --kernel-memory options
The `--kernel-memory` flag was still included to allow it to be used with
old API versions, but it's no longer supported by the kernel, and no longer
handled by OCI runtimes, so deprecating the flags.

With this patch, a deprecation warning is now produced when trying to use
the option;

    docker run --kernel-memory 123b busybox
    Flag --kernel-memory has been deprecated, this option is deprecated in the kernel and no longer supported

    docker container create --kernel-memory 123b busybox
    Flag --kernel-memory has been deprecated, and no longer supported by the kernel
    31fb57e2c6434490a2892031602be20d0206d3cf0fc281ea25654c46dcb62bac

Note that cobra does not _fail_ the command when using deprecated options;
we could make this a hard failure instead, but may not be worth the effort.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-16 15:36:45 +02:00
a02902eb78 docs: deprecated: complete deprecation of kernel-memory limit
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-16 13:50:16 +02:00
437f1260fd Enable completion for docker images
Signed-off-by: Dorin Geman <dorin.geman@docker.com>
2025-09-15 11:30:40 +03:00
4373ce5f8b Merge pull request #6451 from thaJeztah/fix_stats_bounds
cli/command/container: prevent panic during stats on empty event Actor.ID
2025-09-11 17:27:50 +02:00
9b79e48646 cli/command/container: prevent panic during stats on empty event Actor.ID
This code was missing a check for the ID field before truncating it to a
shorter length for presentation. This would result in a panic if an event
would either have an empty ID field or a shorter length ID;

    panic: runtime error: slice bounds out of range [:12] with length 0

    goroutine 82 [running]:
    github.com/docker/cli/cli/command/container.RunStats.func2({{0x0, 0x0}, {0x0, 0x0}, {0x0, 0x0}, {0x40001fcba0, 0x9}, {0x40001fcba9, 0x5}, ...})
        /go/src/github.com/docker/cli/cli/command/container/stats.go:146 +0x1d0
    created by github.com/docker/cli/cli/command/container.(*eventHandler).watch in goroutine 6
        /go/src/github.com/docker/cli/cli/command/container/stats.go:363 +0x1c8

We need to look at this code in general; the truncated ID is passed to
NewStats, which uses the ID to propagate the `Container` field in the
`StatsEntry` struct. which is not used in the default format used by
`docker stats` and, having the same content as the `ID` field on the
same struct, doesn't make it very useful, other than being able to
present it under a `CONTAINER` column (instead of `CONTAINER ID`);
we should consider deprecating it; there may be some subtle things
to look into here; the `Container` field originally held the container
name. This was changed in [moby@ef915fd], which introduced separate
`ID` and `Name` fields, renaming the old `Name` field to container.

Looking at [`Stats.SetStatistics()`] and related code in [stats_helpers.go],
the `Container` field is used as the "canonical" reference for the stats
record; this allows the stats _data_ to be refreshed when a new stats
sample arrives for the same container (also see [moby@929a77b], which
moved locking to the `Stats` wrapper struct). This construct allows to
account for intermediate states, where a stats sample was incomplete
or could produce an error; in that case, the reference to the container
for which the stats were sampled is kept to allow removing a container
from the list once the container was removed. We should consider removing
`Container` as a formatting option, and moving the `Container` field to
the outer struct; this makes the outer struct responsible for keeping a
reference to the container, allowing the `StatsEntry` as a whole to be
replaced atomically.

This patch only addresses the panic;

- It changes the logic to preserve the container ID verbatim instead
  of truncating. This allows stats samples to be matched against the
  `Actor.ID` as-is.
- Truncating the `Container` is moved to the presentation logic;
  currently this does not take `--no-trunc` into account to keep
  the existing behavior, but we can (should) consider adding this.
- Logging is improved to use structured logs, and an extra check is
  added to prevent empty IDs from being added as watcher.

[`Stats.SetStatistics()`]: 82281087e3/cli/command/container/formatter_stats.go (L88-L94)
[moby@ef915fd]: ef915fd036
[moby@929a77b]: 929a77b814
[stats_helpers.go]: 82281087e3/cli/command/container/stats_helpers.go (L26-L51)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-11 14:28:25 +02:00
b9314938b7 cli/command/container: improve TestContainerStatsContext
- Use sub-tests
- Don't use un-named keys
- Add test-cases for 'Name', 'ID' and custom container names

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-11 14:10:54 +02:00
b8cda96d11 cli/command/container: improve TestContainerStatsContext
- Don't use unnamed keys
- Use sub-tests
- Add test-cases for Name and ID fields

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-11 13:58:50 +02:00
82281087e3 Merge pull request #6448 from thaJeztah/cleanup_completion
cli/command/completion: change signatures to return a cobra.CompletionFunc
2025-09-10 15:08:18 +02:00
c3ceba2548 Merge pull request #6447 from thaJeztah/default_completion
cli: disable file-completion by default
2025-09-10 15:07:03 +02:00
e78f16961d Merge pull request #6449 from thaJeztah/context_name_positional
cli/command/context: split name from options struct
2025-09-10 15:05:43 +02:00
d391d0fa4a Merge pull request #6445 from thaJeztah/add_plugin_completions
cli/command/plugin: add completion for plugin subcommands
2025-09-10 15:04:43 +02:00
2f6abcf3c2 Merge pull request #6446 from thaJeztah/fix_completions
Improve shell completion for `docker secret` and `docker config` subcommands
2025-09-10 14:30:05 +02:00
ea8212ab55 cli/command/context: split name from options struct
Name is a required argument for both "create" and "update", so better
to split it from the options struct.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 12:15:50 +02:00
3785198c6e cli/command/completion: change FileNames to return a cobra.CompletionFunc
It's adding a slight indirection by constructing a function when called,
but makes the completion functions more consistent, the signature easier
to read, and making the return type a [cobra.CompletionFunc] makes it
more transparent what it's intended for, and helps discovery of functions
that provide completion.

[cobra.CompletionFunc]: https://pkg.go.dev/github.com/spf13/cobra#CompletionFunc

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 12:06:43 +02:00
531f7e121d cli/command/completion: change Platforms to return a cobra.CompletionFunc
It's adding a slight indirection by constructing a function when called,
but makes the completion functions more consistent, the signature easier
to read, and making the return type a [cobra.CompletionFunc] makes it
more transparent what it's intended for, and helps discovery of functions
that provide completion.

[cobra.CompletionFunc]: https://pkg.go.dev/github.com/spf13/cobra#CompletionFunc

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 12:06:42 +02:00
810be9fbe9 cli/command/completion: change Platforms to return a cobra.CompletionFunc
It's adding a slight indirection by constructing a function when called,
but makes the completion functions more consistent, the signature easier
to read, and making the return type a [cobra.CompletionFunc] makes it
more transparent what it's intended for, and helps discovery of functions
that provide completion.

[cobra.CompletionFunc]: https://pkg.go.dev/github.com/spf13/cobra#CompletionFunc

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 12:06:42 +02:00
d18af47d0f cli/command/completion: change EnvVarNames to return a cobra.CompletionFunc
It's adding a slight indirection by constructing a function when called,
but makes the completion functions more consistent, the signature easier
to read, and making the return type a [cobra.CompletionFunc] makes it
more transparent what it's intended for, and helps discovery of functions
that provide completion.

[cobra.CompletionFunc]: https://pkg.go.dev/github.com/spf13/cobra#CompletionFunc

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 12:06:39 +02:00
47b1715d6f cli-plugins: disable file-completion by default
This uses the DefaultShellCompDirective feature which was added
in cobra to override the default (which would complete to use
files for commands and flags).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 11:52:32 +02:00
78c54646c3 cli: disable file-completion by default
This uses the DefaultShellCompDirective feature which was added
in cobra to override the default (which would complete to use
files for commands and flags).

Note that we set "cobra.NoFileCompletions" for many commands, which
is redundant with this change, so we could remove as well.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 11:52:32 +02:00
467fcfe4bd cli/command/plugin: add completion for plugin subcommands
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 11:49:16 +02:00
8325214ccc cli/command: fix completion for secret create, config create
These commands accept two arguments; the first is a custom name,
the second is either a filename or "-" to create from STDIN.

With this patch:

    # does not provide completion
    docker secret create <tab>

    # starts providing completion once a non-empty name is provided
    docker secret create somename<tab>
    file.txt other-file.txt

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 11:45:21 +02:00
924dd4710b cli/command/secret: fix completion to complete names, not IDs
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 11:45:21 +02:00
d67dad3fb4 cli/command/secret: remove completion for "ls"
This command takes no arguments, so should not provide completion.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 11:40:42 +02:00
1336f51e6a remove redundant closures around completion functions
These were remnants from some earlier implementation.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 11:40:42 +02:00
c5467b5556 Merge pull request #6246 from mmorel-35/revive/use-errors-new
ci: enable use-errors-new from revive
2025-09-10 11:38:14 +02:00
8b23c2bcb5 Merge pull request #6444 from thaJeztah/inspect_completion
cli/command/system: add shell completion for "docker inspect"
2025-09-10 11:36:23 +02:00
22dda0fc2b ci: enable use-errors-new from revive
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2025-09-10 11:31:26 +02:00
69972b682b Merge pull request #6441 from thaJeztah/stderrs
cli/command: replace remaining uses of pkg/errors for stdlib
2025-09-10 10:20:21 +02:00
8bb5595f28 cli/command/system: add shell completion for "docker inspect"
The "docker inspect" command can inspect any type of object, which would
require all possible endpoints to be contacted. By default, we don't
provide completion, but if a `--type` is passed, we provide completion
for the given type.

For example, `docker inspect --type container` will complete container
names, `docker inspect --type volume` will complete volume names and
so on.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-10 00:28:08 +02:00
5245e20866 update vendor
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 19:58:08 +02:00
c0e37dda14 cli/command/container: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 19:58:08 +02:00
b774e75931 cli/command/system: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 19:58:07 +02:00
9ba1314d3a cli/command/system: fix error formatting (errlint)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 19:58:07 +02:00
c5150177bf cli/command/secret: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 19:58:07 +02:00
573e0bddef cli/command/service: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 19:58:07 +02:00
b057ab6d98 cli/command/stack: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 19:58:07 +02:00
0e4934d36c cli/command/swarm: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 19:58:06 +02:00
cd583313ee cli/command/plugin: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 19:58:06 +02:00
5c8817b1b2 cli/command/node: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 19:58:06 +02:00
bf78331f0c cli/command/container: runUpdate: use struct-literal
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 19:58:06 +02:00
0c3bb6c0a4 cli/command/container: rename: remove renameOptions
Also remove redundant validation that's already performed by the client
or daemon.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 19:57:42 +02:00
179dc0228c cli/command/image: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-09 17:23:58 +02:00
f10041c724 Merge pull request #6439 from thaJeztah/plugin_rm_special_handling
cli/command/plugin: remove special error handling on install, upgrade
2025-09-09 11:59:51 +02:00
62d25205e9 Merge pull request #6440 from thaJeztah/use_multierror
use native errors.Join for multi-errors on "docker ps", "docker node ls"
2025-09-09 10:47:23 +02:00
fb3f2da50e cli/command/plugin: remove special error handling on install, upgrade
Similar to 323fbc485e - this code was added
in [moby@c127d96], but used string-matching to detect cases where a user
tried to install an image as plugin. However, this handling no longer matched
any error-strings, so no longer worked:

    docker plugin install busybox
    Error response from daemon: did not find plugin config for specified reference docker.io/library/busybox:latest

[moby@c127d96]: c127d9614f

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-08 20:37:03 +02:00
5df02441ca cli/command/containers: runUpdate: use native errors.Join
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-08 20:30:41 +02:00
935df8a78f cli/command/node: runPs: use native errors.Join
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-08 20:30:41 +02:00
183337db9a Merge pull request #6438 from thaJeztah/internalize_loader
cli/command/stack: internalize GetConfigDetails, LoadComposefile, RunDeploy, RunRemove
2025-09-08 17:31:28 +02:00
d62d370c23 Merge pull request #6435 from thaJeztah/templates_extract_formatJSON
templates: add formatJSON func
2025-09-08 17:30:37 +02:00
26bb688ed0 cli/command/stack: internalize RunDeploy, RunRemove
These were deprecated in ad6ab189a6 and
were only used internally. Move them back inside the stack package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-08 16:03:38 +02:00
73677146f4 cli/command/stack: internalize GetConfigDetails, LoadComposefile
These were deprecated in ad6ab189a6 and
were only used internally. Move them back inside the stack package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-08 15:24:35 +02:00
c24b62f19c templates: add formatJSON func
Move the function used to format as JSON to a separate function instead
of definining it inline.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-08 12:06:46 +02:00
be97096566 Merge pull request #6434 from thaJeztah/update_authors
update mailmap and authors
2025-09-06 00:18:07 +02:00
c4a87de3ec update mailmap and authors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-06 00:06:56 +02:00
4df42ef1d9 Merge pull request #6395 from thaJeztah/bump_engine
vendor: github.com/moby/moby/api v1.52.0-beta.1, client v0.1.0-beta.0
2025-09-05 23:39:16 +02:00
b55fed5ef6 vendor: github.com/moby/moby/api v1.52.0-beta.1, client v0.1.0-beta.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Co-authored-by: Austin Vazquez <austin.vazquez@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-05 22:41:03 +02:00
9fb049c8b6 Merge pull request #6433 from thaJeztah/bump_cdi
vendor: tags.cncf.io/container-device-interface v1.0.1
2025-09-05 22:27:10 +02:00
35f5a4313b vendor: tags.cncf.io/container-device-interface v1.0.1
full diff: https://github.com/cncf-tags/container-device-interface/compare/v0.8.1...v1.0.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-05 21:47:23 +02:00
589f56c345 Merge pull request #6432 from thaJeztah/rm_windows_warning
cli/command/image: build: remove permissions warning on Windows
2025-09-05 09:51:00 -07:00
fa5b741c9b Merge pull request #6430 from austinvazquez/update-build-cache-prune-options
Set `ReservedSpace` field in preparation of `KeepStorage` deprecation
2025-09-05 16:28:53 +02:00
af65ee4584 cli/command/image: build: remove permissions warning on Windows
This warning was added in [moby@4a8b3ca] to print a warning when building
Linux images from a Windows client. Window's filesystem does not have an
"executable" bit, which mean that, for example, copying a shell script
to an image during build would lose the executable bit. So for Windows
clients, the executable bit would be set on all files, unconditionally.

Originally this was detected in the client, which had direct access to
the API response headers, but when refactoring the client to use a common
library in [moby@535c4c9], this was refactored into a `ImageBuildResponse`
wrapper, deconstructing the API response into an `io.Reader` and a string
field containing only the `OSType` header.

This was the only use and only purpose of the `OSType` field, and now that
BuildKit is the default builder for Linux images, this warning didn't get
printed unless BuildKit was explicitly disabled.

This patch removes the warning, so that we can potentially remove the
field, or the `ImageBuildResponse` type altogether.

[moby@4a8b3ca]: 4a8b3cad60
[moby@535c4c9]: 535c4c9a59

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-05 13:35:07 +02:00
7d85d8fbea Set ReservedSpace field in preparation of KeepStorage deprecation
This change updates the builder prune command to send the `ReservedSpace` parameter in preparation of `KeepStorage` deprecation in API v1.52.

Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
2025-09-04 17:47:42 -05:00
4ceef7d328 Merge pull request #6429 from thaJeztah/bump_cobra
vendor github.com/spf13/pflag v1.0.10, github.com/spf13/cobra v1.10.1
2025-09-04 15:46:36 -07:00
3bbb633e5b Merge pull request #6427 from thaJeztah/avoid_client_types_in_opts
don't wrap client options
2025-09-04 15:45:00 -07:00
fef8773bae vendor: github.com/spf13/cobra v1.10.1
full diff: https://github.com/spf13/cobra/compare/v1.9.1...v1.10.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-04 23:13:26 +02:00
aba5962ffb vendor: github.com/spf13/pflag v1.0.10
full diff: https://github.com/spf13/pflag/compare/v1.0.6...v1.0.10

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-04 23:12:13 +02:00
1f12d795db Merge pull request #6426 from docker/dependabot/github_actions/actions/setup-go-6
build(deps): bump actions/setup-go from 5 to 6
2025-09-04 12:27:51 -07:00
e7d14d905e cli/command: don't wrap client options
We may still change this, but in the client module, the signature
of the client.Opt changed to now include a non-exported type, which
means that we can't construct a custom option that is implemented
using client options:

    #18 16.94 # github.com/docker/cli/cli/context/docker
    #18 16.94 cli/context/docker/load.go:105:29: cannot use withHTTPClient(tlsConfig) (value of type func(*client.Client) error) as client.Opt value in argument to append
    #18 16.94 cli/context/docker/load.go:152:6: cannot use c (variable of type *client.Client) as *client.clientConfig value in argument to client.WithHTTPClient(&http.Client{…})

We can consider exporting the `client.clientConfig` type (but keep its
fields non-exported), but for this use, we don't strictly need it, so
let's change the implementation to not having to depend on that.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-04 18:57:07 +02:00
b0b0e457f0 cli/context/docker: don't wrap client options
We may still change this, but in the client module, the signature
of the client.Opt changed to now include a non-exported type, which
means that we can't construct a custom option that is implemented
using client options:

    #18 16.94 # github.com/docker/cli/cli/context/docker
    #18 16.94 cli/context/docker/load.go:105:29: cannot use withHTTPClient(tlsConfig) (value of type func(*client.Client) error) as client.Opt value in argument to append
    #18 16.94 cli/context/docker/load.go:152:6: cannot use c (variable of type *client.Client) as *client.clientConfig value in argument to client.WithHTTPClient(&http.Client{…})

We can consider exporting the `client.clientConfig` type (but keep its
fields non-exported), but for this use, we don't strictly need it, so
let's change the implementation to not having to depend on that.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-04 18:53:06 +02:00
44e66a97a9 build(deps): bump actions/setup-go from 5 to 6
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 11:32:37 +00:00
27b316fc0d Merge pull request #6421 from vvoland/update-go
update to go1.24.7
2025-09-03 21:28:52 +02:00
f64b8a332d update to go1.24.7
This includes 1 security fix:

- net/http: CrossOriginProtection bypass patterns are over-broad

    When passing patterns to CrossOriginProtection.AddInsecureBypassPattern,
    requests that would have redirected to those patterns (e.g. without a trailing
    slash) were also exempted, which might be unexpected.

    Thanks to Marco Gazerro for reporting this issue.

    This is CVE-2025-47910 and Go issue https://go.dev/issue/75054.

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

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-09-03 20:40:59 +02:00
06ed23d5fe Merge pull request #6419 from thaJeztah/complete_pull
add completion for docker image pull
2025-09-03 17:34:50 +02:00
5bf3c6793d add completion for docker image pull
With this patch, completion is provided for images already present
in the local image cache to help pulling the latest version of the
same tag;

    docker pull go<tab>
    golang:1.12    golang:1.18.0  golang:1.21    golang:1.24    gopher:latest
    golang:1.13    golang:1.20    golang:1.23    golang:latest

    docker pull golang:<tab>
    1.12    1.13    1.18.0  1.20    1.21    1.23    1.24    latest

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-03 15:39:41 +02:00
5bce5e17af Merge pull request #6415 from thaJeztah/plugin_no_regex
cli-plugins/manager: replace pluginNameRe for isValidPluginName utility
2025-09-02 16:48:11 -07:00
0640306406 Merge pull request #6417 from thaJeztah/context_no_regex
cli/context/store: replace restrictedNamePattern for isValidName utility
2025-09-02 16:45:21 -07:00
ab7018b590 cli/context/store: replace restrictedNamePattern for isValidName utility
The restrictedNamePattern was a basic regular expression. Replace it
with a minimal utility to do the same, without having to use regular
expressions (or the "lazyregexp" package).

Some quick benchmarking (not committed) show that the non-regex approach
is ~18x faster:

    BenchmarkIsValidName_Regex_Valid-10        8516511        119.4   ns/op      0 B/op        0 allocs/op
    BenchmarkIsValidName_Manual_Valid-10     172426240          6.964 ns/op      0 B/op        0 allocs/op

    BenchmarkIsValidName_Regex_Invalid-10     34824540         34.22  ns/op      0 B/op        0 allocs/op
    BenchmarkIsValidName_Manual_Invalid-10   550804021          2.173 ns/op      0 B/op        0 allocs/op

    BenchmarkIsValidName_Regex_Parallel-10    69289900         17.30   ns/op     0 B/op        0 allocs/op
    BenchmarkIsValidName_Manual_Parallel-10 1000000000          0.9296 ns/op     0 B/op        0 allocs/op

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-02 23:09:12 +02:00
2351f5b915 cli-plugins/manager: replace pluginNameRe for isValidPluginName utility
The pluginNameRe was a basic regular expression, effectively only checking
if the name consisted of lowercase alphanumeric characters. Replace it
with a minimal utility to do the same, without having to use regular
expressions (or the "lazyregexp" package).

Some quick benchmarking (not committed) show that the non-regex approach
is ~25x faster:

    BenchmarkIsValidPluginName_Regex_Valid-10       13956240        81.39  ns/op       0 B/op        0 allocs/op
    BenchmarkIsValidPluginName_Manual_Valid-10     360003060         3.318 ns/op       0 B/op        0 allocs/op

    BenchmarkIsValidPluginName_Regex_Invalid-10     35281794        33.74  ns/op       0 B/op        0 allocs/op
    BenchmarkIsValidPluginName_Manual_Invalid-10   906072663         1.320 ns/op       0 B/op        0 allocs/op

    BenchmarkIsValidPluginName_Regex_Parallel-10    96595677        12.04  ns/op       0 B/op        0 allocs/op
    BenchmarkIsValidPluginName_Manual_Parallel-10  1000000000        0.4541 ns/op      0 B/op        0 allocs/op

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-02 22:58:32 +02:00
153bd95158 Merge pull request #6414 from thaJeztah/plugin_rm_deprecated
cli-plugins/manager: remove deprecated types, functions and aliases
2025-09-02 12:17:31 +02:00
ce72a5c28b cli-plugins/manager: remove deprecated metadata aliases
These consts and types were moved to a separate metadata package in commits
292713c887 and 4321293972,
and deprecated in 72f76f2720, 5876b2941c,
and 6fa7d18320.

This removes the deprecated aliases in `cli-plugins/manager` in favor of
their equivalent in `cli-plugins/manager/metadata`:

- `CommandAnnotationPlugin`
- `CommandAnnotationPluginVendor`
- `CommandAnnotationPluginVersion`
- `CommandAnnotationPluginInvalid`
- `CommandAnnotationPluginCommandPath`
- `NamePrefix`
- `MetadataSubcommandName`
- `HookSubcommandName`
- `Metadata`
- `ReexecEnvvar`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-02 11:43:32 +02:00
d54c7f9e63 cli-plugins/manager: remove deprecated IsNotFound
These errors satisfy errdefs.IsNotFound, which can be used instead. This
function was deprecated in 71460215d3 and
is no longer used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-01 18:31:41 +02:00
6ec32660e9 Merge pull request #6412 from thaJeztah/deprecate_OauthLoginEscapeHatchEnvVar
cli/command/registry: deprecate OauthLoginEscapeHatchEnvVar
2025-09-01 17:57:31 +02:00
9e52a2817c Merge pull request #6410 from thaJeztah/deprecate_ReexecEnvvar
cli-plugins/manager: deprecate ReexecEnvvar
2025-09-01 17:56:42 +02:00
18cdc25bb4 cli/command/registry: deprecate OauthLoginEscapeHatchEnvVar
This const was added in 846ecf59ff, but
only used internally. This patch deprecates the const, to be removed
in the next release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-01 17:43:39 +02:00
6fa7d18320 cli-plugins/manager: deprecate ReexecEnvvar
This alias was added in 4321293972, which is
part of v28.0, but did not deprecate them. They are no longer used in the
CLI itself, but may be used by cli-plugin implementations.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-01 17:33:32 +02:00
89874983c8 Merge pull request #6401 from thaJeztah/check_DisableFlagsInUseLine
verify that DisableFlagsInUseLine is set for all commands
2025-09-01 13:55:57 +02:00
88f68273fd Merge pull request #6406 from thaJeztah/rm_GetStacks_StackWrite
cli/command/stack: move GetStacks and StackWrite internal
2025-09-01 13:55:09 +02:00
a2c886251c Merge pull request #6407 from thaJeztah/rm_exported_context_funcs
cli/command/context: remove deprecated types and functions
2025-09-01 12:58:49 +02:00
9a6cbbc586 Merge pull request #6408 from thaJeztah/rm_NoComplete
cli/command/completion: remove deprecated NoComplete
2025-09-01 12:58:20 +02:00
70915196cb cli/command/completion: remove deprecated NoComplete
This function was an exact duplicate of [cobra.NoFileCompletions], and
was deprecated in 2827d037ba.

[cobra.NoFileCompletions]: https://pkg.go.dev/github.com/spf13/cobra@v1.9.1#NoFileCompletions

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-01 10:27:34 +02:00
9477941c20 cli/command/context: remove deprecated types and functions
These functions and types are shallow wrappers around the context
store and were intended for internal use as implementation for the
CLI itself.

They were exported in 3126920af1 to be
used by plugins and Docker Desktop. However, there's currently no public
uses of this, and Docker Desktop does not use these functions. These were
deprecated in 95eeafa551 and are no longer
used.

This patch removes the deprecated functions as they were meant to be
implementation specific for the CLI. If there's a need to provide
utilities for manipulating the context-store other than through the
CLI itself, we can consider creating an SDK for that purpose.

This removes:

- `RunCreate` and `CreateOptions`
- `RunExport` and `ExportOptions`
- `RunImport`
- `RunRemove` and `RemoveOptions`
- `RunUpdate` and `UpdateOptions`
- `RunUse`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-01 10:22:28 +02:00
6647e229be cli/command/stack: move GetStacks and StackWrite internal
These were deprecated in 036d3a6bab and
30774ed1f2, and were originally in the
cli/command/stack package, but moved for the (now deprecated) Compose
on Kubernetes feature in 4d947de292.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-01 09:40:44 +02:00
0adaf6be3b verify that DisableFlagsInUseLine is set for all commands
This replaces the visitAll recursive function with a test that verifies that
the option is set for all commands and subcommands, so that it doesn't have
to be modified at runtime.

We currently still have to loop over all functions for the setValidateArgs
call, but that can be looked at separately.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-01 09:39:46 +02:00
ba21666654 Merge pull request #6404 from thaJeztah/deprecate_nocomplete
cli/command/completion: deprecate NoComplete
2025-09-01 09:13:18 +02:00
321100e38b Merge pull request #6402 from thaJeztah/deprecate_context_funcs
cli/command/context: deprecate exported types and functions
2025-09-01 09:11:28 +02:00
5dd52a9efa Merge pull request #6397 from thaJeztah/compose_clean
cli/compose/convert: split exported AddStackLabel from implementation
2025-09-01 09:10:42 +02:00
2827d037ba cli/command/completion: deprecate NoComplete
This function was an exact duplicate of [cobra.NoFileCompletions], so
deprecating it in favor of that.

[cobra.NoFileCompletions]: https://pkg.go.dev/github.com/spf13/cobra@v1.9.1#NoFileCompletions

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-30 12:00:34 +02:00
95eeafa551 cli/command/context: deprecate exported types and functions
These functions and types are shallow wrappers around the context
store and were intended for internal use as implementation for the
CLI itself.

They were exported in 3126920af1 to be
used by plugins and Docker Desktop. However, there's currently no public
uses of this, and Docker Desktop does not use these functions.

This patch deprecates the exported functions as they were meant to be
implementation specific for the CLI. If there's a need to provide
utilities for manipulating the context-store other than through the
CLI itself, we can consider creating an SDK for that purpose.

This deprecates:

- `RunCreate` and `CreateOptions`
- `RunExport` and `ExportOptions`
- `RunImport`
- `RunRemove` and `RemoveOptions`
- `RunUpdate` and `UpdateOptions`
- `RunUse`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-30 01:51:12 +02:00
81ea282e00 Merge pull request #6398 from thaJeztah/rm_deprecated_stack
cli/command/stack: remove some deprecated functions
2025-08-29 17:23:29 +01:00
0155c264ae Merge pull request #6371 from Benehiko/support-fallback-negative-certs
Add escape hatch for GODEBUG=x509negativeserial
2025-08-29 15:59:24 +02:00
77205e782a cli/command/stack/swarm: deployServices: use struct-literal for options
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-29 14:52:38 +02:00
580c3aa218 cli/compose/convert: Networks: use struct-literal for IPAM config
Use a struct-literal for the IPAM config, and combine some of the checks.
Also use the Name field as a default, and only construct a scoped name
if the given name is empty (instead of the reverse).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-29 14:50:49 +02:00
f8d33f4602 cli/compose/convert: split exported AddStackLabel from implementation
This function is currently only used within the package; create a non-exported
version of it, to make it clear it's not used elsewhere. This patch keeps
the exported function for now, but we can decide if we need to keep it
in future.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-29 14:50:46 +02:00
2066dbcfe8 cli/command/stack/swarm: inline validateResolveImageFlag
It was only used in a single place, and possibly incorrect. Let's inline
it to put the logic where it's used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-29 14:39:06 +02:00
c3be589c16 cli/command/stack/swarm: remove deprecated RunPS and options.PS
These were deprecated in f0e5a0d654 and
036d3a6bab and were only used internally.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-29 14:39:06 +02:00
2a05951680 cli/command/stack: remove deprecated RunServices and swarm.GetServices
These were deprecated in f0e5a0d654,
036d3a6bab, and
d16c560664 and were only used internally.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-29 14:39:00 +02:00
72f79333e5 return early if GODEBUG set or context is default
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-29 14:31:21 +02:00
6163c03b11 rename function to fit what it is doing
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-29 14:08:15 +02:00
467305fcea Test setAllowNegativex509
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-29 12:15:49 +02:00
65a6c35d90 Cleanup setAllowNegativex509
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-29 12:15:29 +02:00
c4df0d17bb cli/command/stack: remove deprecated RunList and options.List
These were deprecated in f0e5a0d654 and
d16c560664 and were only used internally.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-29 11:46:21 +02:00
b3cd9d48fe Merge pull request #6393 from thaJeztah/cleanup_stacks
cli/command/stack: cleanups and optimizations
2025-08-29 11:22:11 +02:00
581cb2b70a cli/command/stack/swarm: GetStacks: don't use pointers for values
These are very small structs, so using pointers doesn't bring much
advantage and makes it slightly more cumbersome to use.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-28 15:44:09 +02:00
6b86aac02e cli/command/stack/formatter: StackWrite: remove intermediate vars
- inline the closure
- remove newStackContext() constructor and inline it

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-28 15:37:52 +02:00
8b01d8e74c cli/command/stack: runList: remove intermediate slice
This intermediate slice was a left-over from the "Compose on Kubernetes"
feature, which required some conversions, but that code was removed in
193ede9b12, so the intermediate slice no
longer has a purpose.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-28 15:23:40 +02:00
047ea37054 cli/command/stack/swarm: pruneServices: fix typo and minor cleanup
- fix typo in argument name
- rename var that shadowed function
- pre-allocate slice

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-28 15:22:16 +02:00
4f7f42df0e cli/command/stack/swarm: GetStacks: tidy up
Preserve the original order by avoiding the intermediate map[string] and
keeping an index for the first occurrence of a stack; this also avoids
looping multiple times.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-28 15:22:12 +02:00
5a23ff9b17 cli/command/stack/formatter: TestStackContextWrite: cleanup test
- Include name in test-table
- Don't use un-keyed values in struct
- Simplify test-table to take a format string instead of a whole formatter.Context

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-28 15:21:16 +02:00
8f25f4fb24 Merge pull request #6389 from thaJeztah/deprecate_stack_commands
cli/command/stack/*: deprecate exported functions and types
2025-08-28 13:55:46 +02:00
d16c560664 cli/command/stack: deprecate RunList, RunServices
Functions and types in this package were exported as part of the "compose
on kubernetes" feature, which was deprecated and removed. These functions
are meant for internal use, and will be removed in the next release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-28 10:21:47 +02:00
036d3a6bab deprecate cli/command/stack/swarm
Functions and types in this package were exported as part of the "compose
on kubernetes" feature, which was deprecated and removed. These functions
are meant for internal use, and will be removed in the next release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-28 10:21:47 +02:00
f0e5a0d654 deprecate cli/command/stack/options
Functions and types in this package were exported as part of the "compose
on kubernetes" feature, which was deprecated and removed. These functions
are meant for internal use, and will be removed in the next release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-28 10:21:47 +02:00
ad6ab189a6 deprecate cli/command/stack/loader
Functions and types in this package were exported as part of the "compose
on kubernetes" feature, which was deprecated and removed. These functions
are meant for internal use, and will be removed in the next release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-28 10:21:47 +02:00
30774ed1f2 deprecate cli/command/stack/formatter
Functions and types in this package were exported as part of the "compose
on kubernetes" feature, which was deprecated and removed. These functions
are meant for internal use, and will be removed in the next release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-28 10:21:42 +02:00
306b7445a1 Merge pull request #6377 from thaJeztah/trust_unconvert
cli/command/trust: unconvert
2025-08-27 12:31:52 -07:00
ef0a67551a Merge pull request #6382 from thaJeztah/rm_ParseEnvFile
opts: remove deprecated ParseEnvFile
2025-08-27 12:25:54 -07:00
a7df96501f Merge pull request #6378 from thaJeztah/system_no_deprecated
cli/command/system: don't use deprecated fields in test
2025-08-27 12:21:41 -07:00
5a2f87f6f6 Merge pull request #6385 from thaJeztah/bump_go_events
vendor: github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32
2025-08-27 11:14:04 -07:00
b8507d71e8 Merge pull request #6387 from thaJeztah/bump_modules2
vendor: github.com/moby/moby/api, github.com/moby/moby/client master
2025-08-27 17:38:15 +02:00
cdf705ce66 vendor: github.com/moby/moby/api, github.com/moby/moby/client master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-27 17:12:11 +02:00
05220c5f19 vendor: github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32
full diff: e31b211e4f...c867878c5e

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-27 15:51:19 +02:00
9e331b55d6 opts: remove deprecated ParseEnvFile
This was deprecated in e650803f09 and
no longer used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-27 15:03:34 +02:00
f40caed86c Merge pull request #6376 from thaJeztah/update_TestEventsFormat
cli/command/system: TestEventsFormat: remove use of deprecated fields
2025-08-27 14:45:44 +02:00
3d87aa441f cli/command/system: don't use deprecated fields in test
This only impacts the JSON marshaled output; the "regular" output
of `docker info` already ignores these fields.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-27 14:43:37 +02:00
4d9017d789 cli/command/trust: unconvert
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-27 14:40:56 +02:00
c558c30056 Merge pull request #6374 from thaJeztah/plugin_errs
cli/command/image: remove special handling for plugin errors on pull
2025-08-27 13:47:39 +02:00
823c6a75b3 cli/command/system: TestEventsFormat: remove use of deprecated fields
These were just testing JSON marshaling fields that are deprecated, but
may be present in a response; these fields will be removed in future
API versions, so stop testing for them.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-27 13:45:05 +02:00
7dfa471387 Merge pull request #6375 from thaJeztah/bump_modules
vendor: github.com/moby/moby/api, github.com/moby/moby/client 62884141100c
2025-08-27 12:14:47 +02:00
10072c3548 vendor: github.com/moby/moby/api, github.com/moby/moby/client 62884141100c
full diffs:

- 7145e7666b...6288414110

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-27 10:21:32 +02:00
323fbc485e cli/command/image: remove special handling for plugin errors on pull
This special handling was added in [moby@9b6dcc8], and later updated in
[moby@c127d96], but it fully depended on string-matching, which is brittle.
Testing the original ticket that lead to this handling, it looks like the
string matching no longer works, and the daemon error is returned as-is:

With graphdrivers:

    docker pull tiborvass/no-remove
    Using default tag: latest
    Error response from daemon: Encountered remote "application/vnd.docker.plugin.v0+json"(unknown) when fetching

With containerd snapshotters enabled:

    docker pull tiborvass/no-remove
    Using default tag: latest
    latest: Pulling from tiborvass/no-remove
    cf635291f7c9: Download complete
    failed to unpack image on snapshotter overlayfs: mismatched image rootfs and manifest layers

The error-message for containerd can probably be improved, but as the special
handling in the CLI no longer works, we can remove it.

[moby@9b6dcc8]: 9b6dcc8b9d
[moby@c127d96]: c127d9614f

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-26 23:47:17 +02:00
701b678104 Merge pull request #6255 from thaJeztah/bump_modules
vendor: github.com/moby/moby/api, moby/moby/client 7145e7666b8f (master)
2025-08-26 13:37:06 -07:00
7118f1fb4b vendor: github.com/moby/moby/api, moby/moby/client 7145e7666b8f (master)
full diff:

- https://github.com/docker/docker/compare/api/v1.52.0-alpha.1...7145e7666b8f
- https://github.com/docker/docker/compare/client/v0.1.0-alpha.0...7145e7666b8f

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

WIP latest

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-26 20:02:30 +02:00
212deb412d Merge pull request #6373 from thaJeztah/rm_image_AuthResolver
cli/command/image: remove deprecated AuthResolver utility
2025-08-26 19:27:22 +02:00
481e792773 cli/command/image: remove deprecated AuthResolver utility
This function was used to share it between "trust" and "image",
but was only a shallow wrapper, so split the implementations where
used.

It was deprecated in 7ad113ccc2 and is
no longer used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-26 17:46:02 +02:00
6bc7ed8b65 Merge pull request #6372 from thaJeztah/plugin_simplify_auth
cli/command/plugin: simplify auth
2025-08-26 17:32:23 +02:00
a2198ecd14 Merge pull request #6366 from thaJeztah/fix_email_deprecation
cli/config/types: update deprecation comment for AuthConfig.Email
2025-08-26 08:25:03 -07:00
f2c8b9dfd3 cli/command/plugin: simplify auth
Now that 3f5b1bdd32 removed DCT, which
needed some of the intermediate types (indexInfo), we can simplify the
auth code further and just get the base64-encoded AuthConfig to be set
as header.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-26 16:42:34 +02:00
cfd7e543fc Merge pull request #6370 from thaJeztah/remove_exported_config_funcs
cli/command/config: remove deprecated types and functions
2025-08-26 16:35:23 +02:00
7d7a7aac4d Add escape hatch for GODEBUG=x509negativeserial
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-26 16:07:47 +02:00
250e4a564c cli/command/config: remove deprecated types and functions
These were deprecated in a5f4ba08d9 and only
used internally.

This removes the deprecated types and functions:

- `RunConfigCreate` and  `CreateOptions`
- `RunConfigInspect` and `InspectOptions`
- `RunConfigList` and `ListOptions`
- `RunConfigRemove` and `RemoveOptions`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-26 13:17:53 +02:00
fda64ebc64 Merge pull request #6368 from thaJeztah/deprecate_exported_config_funcs
cli/command/config: deprecate exported types and functions
2025-08-26 13:17:12 +02:00
a5f4ba08d9 cli/command/config: deprecate exported types and functions
These were exported in f60369dfe6 to be
used in docker enterprise, but this never happened, and there's no
known consumers of these, so we should deprecate these. External
consumers can still call the API-client directly, which should've
been the correct thing to do in the first place.

This deprecates:

- `RunConfigCreate` and  `CreateOptions`
- `RunConfigInspect` and `InspectOptions`
- `RunConfigList` and `ListOptions`
- `RunConfigRemove` and `RemoveOptions`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-25 18:54:20 +02:00
09cd4ea26c Merge pull request #6362 from thaJeztah/cleanup_formatter
cli/command/formatter: ContainerContext: assorted cleanups, fixes
2025-08-25 16:22:06 +02:00
a6826de3e2 Merge pull request #6361 from thaJeztah/cli_trust_cleanups
cli/trust: some cleanups
2025-08-25 16:20:56 +02:00
5bcb60aaa6 Merge pull request #6356 from thaJeztah/unexport_authresolver_util
cli/command/image: deprecate AuthResolver and un-export
2025-08-25 16:19:45 +02:00
1beb3d4d5b Merge pull request #6355 from thaJeztah/rm_image_pull
cli/command/image: remove exported RunPull, PullOptions
2025-08-25 16:00:38 +02:00
0395cdbd71 Merge pull request #6353 from thaJeztah/rm_json_deprecated
internal/jsonstream: remove uses of deprecated fields
2025-08-25 15:52:08 +02:00
aab947de8f cli/config/types: update deprecation comment for AuthConfig.Email
Relates to [cli@27b2797], which forked this type from the Moby API, and
[moby@6cfff7e], which made the same change on the API side.

The Email field was originally used to create a new Docker Hub account
through the `docker login` command. The `docker login` command could be
used both to log in to an existing account (providing only username and
password), or to create a new account (providing desired username and
password, and an e-mail address to use for the new account).

This functionality was confusing, because it was implemented when Docker
Hub was the only registry, but the same functionality could not be used
for other registries. This functionality was removed in Docker 1.11 (API
version 1.23) through [moby@aee260d], which also removed the Email field
([engine-api@9a9e468]) as it was no longer used.

However, this caused issues when using a new CLI connecting with an old
daemon, as the field would no longer be serialized, and the deprecation
may not yet be picked up by custom registries, so [engine-api@167efc7]
added the field back, deprecated it, and added an "omitempty". There
was no official "deprecated" format yet at the time, so let's make sure
the deprecation follows the proper format to make sure it gets noticed.

[cli@27b2797]: 27b2797f7d
[moby@6cfff7e]: 6cfff7e880
[moby@aee260d]: aee260d4eb
[engine-api@9a9e468]: 9a9e468f50
[engine-api@167efc7]: 167efc72bb

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-25 15:44:04 +02:00
5ab12e6262 Merge pull request #6365 from thaJeztah/fix_version_annotations
cli/command/service: fix API annotations for generic resource flags
2025-08-25 13:33:12 +02:00
104b07647f Merge pull request #6363 from thaJeztah/image_push_cleanups
cli/command/image: runPush: minor cleanups and linting issues
2025-08-25 11:47:21 +02:00
27734fdf4d Merge pull request #6349 from thaJeztah/rm_RegistryAuthenticationPrivilegedFunc
cli/command: remove deprecated RegistryAuthenticationPrivilegedFunc
2025-08-25 11:00:30 +02:00
dcc3d25dc2 cli/command/service: fix API annotations for generic resource flags
These flags were added in 20a6ff32ee, and require
API version v1.32 or up, but they accidentally copied the flag-name from another
flag, so were not setting the annotation correctly.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-25 10:59:47 +02:00
c36e67d7b6 cli/command/image: runPush: minor cleanups and linting issues
- Remove redundant intermediate variables
- Explicitly use an early return on error instead of combining with
  other checks.
- Fix unhandled errors and combine defers
- Remove outstanding TODO that unlikely will be addressed

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-24 19:38:24 +02:00
4f944e245b cli/command/formatter: ContainerContext.Image: explicitly strip digest
The `reference.TrimNamed` function strips both digests and tags; the
formatter function only wants to remove the digest, but preserve any
tags present.

Update the implementation to only trim the reference if there's a digest
present, otherwise use it as-is.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-24 19:31:47 +02:00
7ac3e0e0bf cli/command/formatter: ContainerContext.Image: use early returns
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-24 19:31:47 +02:00
0e7d422e5f cli/command/formatter: TestContainerPsContext: add test-cases
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-24 19:31:46 +02:00
7cb8147e77 cli/trust: GetNotaryRepository: inline variables
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-24 16:01:08 +02:00
35a41c39a4 cli/trust: check for Digested, Tagged reference instead of Canonical
The [Canonical] interface defines images that are both [Named] and
[Digested], but in all places where it was used, we were only interested
whether the reference contained a digest. Similarly [NamedTagged] is
a superset of [Tagged], so checking for [Tagged] is sufficient if we're
already dealing with a [Named] reference.

This patch changes those checks to check for [Digested] and [Tagged]
references, as that's what's relevant for these checks.

[Named]: https://pkg.go.dev/github.com/distribution/reference#Named
[NamedTagged]: https://pkg.go.dev/github.com/distribution/reference#NamedTagged
[Canonical]: https://pkg.go.dev/github.com/distribution/reference#Canonical
[Digested]: https://pkg.go.dev/github.com/distribution/reference#Digested

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-24 16:00:54 +02:00
abe4aa7893 Merge pull request #6360 from hsnabszhdn/add-missing-backticks
docs: add missing backticks in 'run.md'
2025-08-23 14:14:58 +02:00
08f876d6e8 Merge pull request #6359 from hsnabszhdn/fix-sentence-structures-in-run-md
docs: fix sentence structures in 'run.md'
2025-08-23 14:14:24 +02:00
0f875ba9ad Merge pull request #6351 from thaJeztah/avoid_shadowing
cli/command: rename vars for consistency and prevent shadowing
2025-08-23 02:56:49 -07:00
d9cafa759f docs: add missing backticks in 'run.md'
Signed-off-by: Hossein Abbasi <16090309+hsnabszhdn@users.noreply.github.com>
2025-08-23 16:14:53 +10:00
ba2c1c94ab docs: fix sentence structures in 'run.md'
Signed-off-by: Hossein Abbasi <16090309+hsnabszhdn@users.noreply.github.com>
2025-08-23 15:54:56 +10:00
7ad113ccc2 cli/command/image: deprecate AuthResolver and un-export
This function was exported to share it between "trust" and "image",
but was only a shallow wrapper, so split the implementations where
used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-23 03:46:14 +02:00
9216f04eb6 cli/command/image: remove exported RunPull, PullOptions
These were exported in 812f113685, but
while the function and options are exported, the option-fields were
all un-exported, so these were not usable.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-23 03:35:51 +02:00
9fd71c8347 cli/command: rename vars for consistency and prevent shadowing
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-23 03:04:36 +02:00
045ac0b159 internal/jsonstream: remove uses of deprecated fields
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-23 02:17:20 +02:00
03da6ad2d1 cli/command: remove deprecated RegistryAuthenticationPrivilegedFunc
This function was deprecated in 29263e865b
and is no longer used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-23 00:41:07 +02:00
1df8feb2e4 Merge pull request #6345 from thaJeztah/bump_x_sync
vendor: golang.org/x/sync v0.16.0
2025-08-22 21:05:54 +02:00
c7cbac58b3 vendor: golang.org/x/sync v0.16.0
Brings in the errgroup implementation for reverted auto-recover from panics.

full diff: https://github.com/golang/sync/compare/v0.14.0...v0.16.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 14:26:38 +02:00
e8e4588f64 Merge pull request #6344 from thaJeztah/remove_builder_newprunecommand
cli/command/trust: remove deprecated NewPruneCommand
2025-08-22 12:41:14 +02:00
d317bc30be cli/command/trust: remove deprecated NewPruneCommand
These were deprecated in 7032f5922e, which
is part of the v28.4 release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 12:35:36 +02:00
832a3754e5 Merge pull request #6342 from thaJeztah/deprecate_builder_NewPruneCommand
cli/command/builder: deprecate NewPruneCommand
2025-08-22 12:35:08 +02:00
4d4533abaa Merge pull request #6338 from thaJeztah/cleanup_formatters
cli/command: inline vars and use struct literals in formatting functions
2025-08-22 12:25:14 +02:00
7032f5922e cli/command/builder: deprecate NewPruneCommand
This patch deprecates exported NewPruneCommand and moves the
implementation details to an unexported function.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 12:18:27 +02:00
2966159873 Merge pull request #6339 from thaJeztah/rm_deprecated_formatting
cli/command/*: remove deprecated formatting-related functions and types
2025-08-22 12:10:26 +02:00
65e7ece518 Merge pull request #6337 from thaJeztah/pretty_io
cli/command/system: prettyPrintVersion: accept a plain io.Writer
2025-08-22 11:31:33 +02:00
5bb8ab4e6f cli/command/trust: remove deprecated formatting functions
These were deprecated in 95c9b1b13b, which
is part of the v28.4 release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:43:27 +02:00
8969b57500 cli/command/task: remove deprecated formatting functions
These were deprecated in c3ee82fdc3, which
is part of the v28.4 release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:42:53 +02:00
c6f4573153 cli/command/service: remove deprecated formatting functions
These were deprecated in 9f453d3fea, which
is part of the v28.4 release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:42:24 +02:00
04bcae3a8c cli/command/secret: remove deprecated formatting functions
These were deprecated in f3088e37a0, which
is part of the v28.4 release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:41:52 +02:00
c592932f47 cli/command/registry: remove deprecated formatting functions
These were deprecated in 83371c2014, which
is part of the v28.4 release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:41:11 +02:00
264080d2fc cli/command/plugin: remove deprecated formatting functions
These were deprecated in bf47419852, which
is part of the v28.4 release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:40:43 +02:00
e5a0fb09a3 cli/command/node: remove deprecated formatting functions
These were deprecated in 123ef81f7d, which
is part of the v28.4 release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:40:09 +02:00
7b172fcf53 cli/command/network: remove deprecated formatting functions
These were deprecated in e3903a1ac8, which
is part of the v28.4 release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:39:35 +02:00
d223ec3b56 cli/command/image: remove deprecated formatting functions
These were deprecated in 15cf4fa912, which
is part of the v28.4 release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:38:56 +02:00
206a8da307 cli/command/container: remove deprecated formatting functions
These were deprecated in 907507e22a and
fdc90caeee, which are part of the v28.4
release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:38:09 +02:00
f969adf63f cli/command/config: remove deprecated formatting functions
These were deprecated in e626f778ec, which
is part of the v28.4 release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:36:11 +02:00
e416418e70 cli/command/checkpoint: remove deprecated formatting functions
These were deprecated in d861b78a8a, which
is part of the v28.4 release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:34:17 +02:00
efd6e7b6e0 cli/command/system: prettyPrintVersion: accept a plain io.Writer
We're only writing to a single stream, so may as well just let it
take an io.writer.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:22:56 +02:00
f72ec26693 cli/command/trust: inline vars and use struct literals
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:19:11 +02:00
6de2cdd1af cli/command/task: inline vars and use struct literals
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:19:11 +02:00
e308036440 cli/command/service: inline vars and use struct literals
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:19:11 +02:00
12d30bb50c cli/command/secret: inline vars and use struct literals
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:19:11 +02:00
863b5633f3 cli/command/registry: inline vars and use struct literals
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:19:11 +02:00
aa39a7e7be cli/command/plugin: inline vars and use struct literals
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:19:10 +02:00
1a433cdbdb cli/command/node: inline vars and use struct literals
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:19:10 +02:00
3d2bd97a82 cli/command/config: formatWrite: inline vars and use struct literals
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:19:10 +02:00
70033b78d4 cli/command/checkpoint: formatWrite: inline vars and use struct literals
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:19:10 +02:00
8cb8056efa cli/command/image: historyWrite: inline vars and use struct literals
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:19:10 +02:00
7589722e93 cli/command/network: formatWrite: inline vars and use struct literals
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:19:09 +02:00
e06e758d5f cli/command/system: TestVersionFormat: use table-test and struct literal
- Use a table-test to more easily allow adding test-cases
- Use the test-name itself as name for the golden file
- Use a struct-literal to create the fixture for formatting.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-22 09:11:35 +02:00
58bb45c37f Merge pull request #6336 from thaJeztah/internalize_formatters
cli/command/*: deprecate formatting-related functions and types
2025-08-22 08:29:16 +02:00
95c9b1b13b cli/command/trust: deprecate formatting-related functions and types
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

This deprecates the following types and functions:

- `SignedTagInfo`
- `SignerInfo`
- `NewTrustTagFormat`
- `NewSignerInfoFormat`
- `TagWrite`
- `SignerInfoWrite`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 15:25:26 +02:00
c3ee82fdc3 cli/command/task: deprecate NewTaskFormat, FormatWrite
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 15:25:26 +02:00
9f453d3fea cli/command/service: deprecate NewFormat, InspectFormatWrite
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 15:25:26 +02:00
f3088e37a0 cli/command/secret: deprecate NewFormat, FormatWrite, InspectFormatWrite
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 15:25:25 +02:00
83371c2014 cli/command/registry: deprecate NewSearchFormat, SearchWrite
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 15:25:25 +02:00
bf47419852 cli/command/plugin: deprecate NewFormat, FormatWrite
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 15:25:25 +02:00
123ef81f7d cli/command/node: deprecate NewFormat, FormatWrite, InspectFormatWrite
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 15:25:25 +02:00
e626f778ec cli/command/config: deprecate NewFormat, FormatWrite, InspectFormatWrite
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 15:25:25 +02:00
d861b78a8a cli/command/checkpoint: deprecate NewFormat, FormatWrite
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 15:25:24 +02:00
15cf4fa912 cli/command/image: deprecate NewHistoryFormat, HistoryWrite
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 15:25:24 +02:00
e3903a1ac8 cli/command/network: deprecate NewFormat, FormatWrite
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 14:31:27 +02:00
b0d1d94711 Merge pull request #6330 from thaJeztah/internalize_ParseEnvFile
opts: deprecate ParseEnvFile
2025-08-21 13:35:39 +02:00
8c0440a653 Merge pull request #6335 from thaJeztah/rm_exported_cobra_funcs
cli/command/*: remove deprecated cobra command constructors
2025-08-21 13:13:19 +02:00
40e605a3b2 Merge pull request #6334 from thaJeztah/commands_nolock
internal/commands: remove mutexes / synchronisation and copy
2025-08-21 13:11:17 +02:00
873609d790 cli/command/*: remove deprecated cobra command constructors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 11:12:28 +02:00
570a17b3bc internal/commands: RegisterLegacy: remove redundant copy
The RegisterLegacy and Register functions register constructors for
commands, so we should expect them to be a fresh copy that is not
shared, which means that we can mutate the command in-place.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 10:47:26 +02:00
4405c0bd50 internal/commands: remove mutexes / synchronisation
The Register and RegisterLegacy functions are designed to be used
in package init() functions, which are guaranteed to be run
sequentially.

From the documentation (https://go.dev/ref/mem#init);

> Program initialization runs in a single goroutine, but that goroutine
> may create other goroutines, which run concurrently. If a package `p`
> imports package `q`, the completion of `q`'s `init` functions happens
> before the start of any of `p`'s.
>
> The completion of all `init` functions is synchronized before the
> start of the function `main.main`.

This patch removes the synchonisation as no concurrency should happen
if these functions are used as intended.

As the internal queue is not expected to be mutated after use, we also
don't have to return a copy.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-21 10:42:02 +02:00
942a6c4c76 Merge pull request #6329 from Benehiko/cli/commands
Register CLI commands implicitly
2025-08-21 09:23:30 +02:00
56cab16779 Register CLI commands implicitly
This patch removes the explicit `commands.AddCommands` function and
instead relies upon the `internal/commands` package which registers each
CLI command using `init()` instead.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-21 07:16:35 +02:00
76c405cfdb Merge pull request #6327 from thaJeztah/registryclient_simplify_repositoryEndpoint
internal/registryclient: repositoryEndpoint: memoize repoName
2025-08-20 15:04:16 +02:00
e650803f09 opts: deprecate ParseEnvFile
It was a wrapper around kvfile.Load, which should be used instead.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-20 15:03:05 +02:00
4d93a6486e Merge pull request #6328 from Benehiko/command/trust
Unexport trust commands
2025-08-20 12:18:16 +00:00
d071c29d4a Merge pull request #6326 from Benehiko/command/plugin
Unexport plugin commands
2025-08-20 12:13:18 +00:00
8d99b45d4a Merge pull request #6325 from Benehiko/command/swarm
Unexport swarm commands
2025-08-20 12:08:40 +00:00
bd8e3e4440 Unexport trust commands
This patch deprecates exported trust commands and moves the implementation
details to an unexported function.

Commands that are affected include:

- trust.NewTrustCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-20 14:07:45 +02:00
c6b7268932 Unexport plugin commands
This patch deprecates exported plugin commands and moves the implementation
details to an unexported function.

Commands that are affected include:

- plugin.NewPluginCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-20 14:03:57 +02:00
2ce94e4fff internal/registryclient: repositoryEndpoint: memoize repoName
- Parse/format the repository name when constructing and store the
  result.
- Remove the Name() accessor, as this type is only used internally,
  and no longer had any special handling.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-20 14:02:26 +02:00
bf39340294 Unexport swarm commands
This patch deprecates exported swarm commands and moves the implementation
details to an unexported function.

Commands that are affected include:

- swarm.NewSwarmCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-20 13:59:30 +02:00
b2b7187244 Merge pull request #6324 from Benehiko/command/registry
Unexport registry commands
2025-08-20 11:43:43 +00:00
7dfcb06587 Merge pull request #6322 from Benehiko/command/context
Unexport context command
2025-08-20 11:40:09 +00:00
d4588c711c Unexport registry commands
This patch deprecates exported registry commands and moves the implementation
details to an unexported function.

Commands that are affected include:

- registry.NewLoginCommand
- registry.NewLogoutCommand
- registry.NewSearchCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-20 13:39:06 +02:00
bf9173f383 Merge pull request #6323 from Benehiko/command/stack
Unexport stack commands
2025-08-20 11:36:40 +00:00
630fe430ff Unexport stack commands
This patch deprecates exported stack commands and moves the implementation
details to an unexported function.

Commands that are affected include:

- stack.NewStackCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-20 13:27:11 +02:00
3b0edc794c Unexport context command
This patch deprecates exported context commands and moves the implementation
details to an unexported function.

Commands that are affected include:

- context.NewContextCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-20 13:22:47 +02:00
89316e18fc Merge pull request #6321 from Benehiko/command/volume
Unexport volume commands
2025-08-20 11:18:35 +00:00
7a50955006 Merge pull request #6320 from Benehiko/command/service
Unexport service commands
2025-08-20 10:57:33 +00:00
9961e39d40 Unexport volume commands
This patch deprecates exported volume commands and moves the implementation
details to an unexported function.

Commands that are affected include:

- volume.NewVolumeCommand
- volume.NewPruneCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-20 12:57:33 +02:00
88178eda32 Unexport service commands
This patch deprecates exported service commands and moves the implementation
details to an unexported function.

Commands that are affected include:

- service.NewServiceCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-20 12:51:55 +02:00
cd859b33b4 Merge pull request #6318 from Benehiko/command/manifest
Unexport manifest command
2025-08-20 12:51:14 +02:00
1d34432676 Merge pull request #6319 from Benehiko/command/secret
Unexport secret commands
2025-08-20 10:49:06 +00:00
e00762ed7d Unexport secret commands
This patch deprecates exported secret commands and moves the implementation
details to an unexported function.

Commands that are affected include:

- secrets.NewSecretCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-20 12:45:11 +02:00
6b58c4d545 Merge pull request #6317 from Benehiko/command/node
Unexport node commands
2025-08-20 12:43:50 +02:00
642adae0c0 Merge pull request #6316 from Benehiko/command/network
Unexport network commands
2025-08-20 12:42:57 +02:00
02fda07211 Unexport manifest command
This patch deprecates exported manifest commands and moves the implementation
details to an unexported function.

Commands that are affected include:

- manifest.NewManifestCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-20 12:40:26 +02:00
ab3fcf9f9b Unexport node commands
This patch deprecates exported node commands and moves the implementation
details to an unexported function.

Commands that are affected include:

- node.NewNodeCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-20 12:34:49 +02:00
78a8856c14 Unexport network commands
This patch deprecates exported network commands and moves the
implementation details to an unexported function.

Commands that are affected include:

- network.NewNetworkCommand
- network.NewPruneCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-20 12:32:09 +02:00
4643b42e1d Merge pull request #6314 from thaJeztah/auth_cleanups
cli/command: fix godoc links and inline resolveAuthConfigFromImage
2025-08-20 11:57:34 +02:00
86e2a06f1b Merge pull request #6296 from thaJeztah/cli_rm_deprecated_utils
cli: remove deprecated VisitAll, DisableFlagsInUseLine utilities
2025-08-20 11:42:45 +02:00
4286883b95 cli/command: inline resolveAuthConfigFromImage
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-19 23:42:07 +02:00
2d3b0b33b4 cli/command: fix godoc links
- Use versioned links to github.com/docker/docker packages
- Fix links to RFC 4648, section 5

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-19 23:42:03 +02:00
c3170f1c81 Merge pull request #6293 from thaJeztah/remove_deprecated_opts
opts: remove deprecated types and functions
2025-08-19 21:03:59 +02:00
73d88f514b Merge pull request #6306 from thaJeztah/remove_quote_handling
cli/flags: remove special quote handling for `--tlsXXX` flags
2025-08-19 11:36:23 -07:00
9ca4ae9e70 Merge pull request #6305 from Benehiko/commands/system
Unexport system commands
2025-08-19 15:27:15 +02:00
11a60e871e Merge pull request #6304 from thaJeztah/internalize_registryclient
cli/registry/client: deprecate and move internal
2025-08-19 15:26:10 +02:00
57ef7eed46 Merge pull request #6303 from thaJeztah/deprecated_nit
docs: deprecated: fix formatting of deprecated/removed in
2025-08-19 15:23:39 +02:00
9b9d103b29 cli/flags: remove special quote handling for --tlsXXX flags
This non-standard handling for these options was added in [moby@e4c1f07]
and [moby@abe32de] to work around a regression in Docker 1.13 that caused
`docker-machine` to fail. Preserving quotes in such cases is expected (and
standard behavior), but versions of Docker before 1.13 used a custom "mflag"
package for flag parsing, and that package contained custom handling for
quotes (added in [moby@0e9c40e]).

Given that Docker Machine reached EOL a long time ago and other options,
such as `docker context`, have been added to configure the CLI to connect
to a specific host (with corresponding TLS configuration), we can remove
the special handling for these flags, as it's inconsistent with all other
flags, and not worth maintaining for a tool that no longer exists.

[moby@e4c1f07]: e4c1f07729
[moby@abe32de]: abe32de6b4
[moby@0e9c40e]: 0e9c40eb82

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-19 15:21:17 +02:00
cfb8cb91f2 Unexport system commands
This patch deprecates exported system commands and moves the
implementation details to an unexported function.

Commands that are affected include:

- system.NewVersionCommand
- system.NewInfoCommand
- system.NewSystemCommand
- system.NewEventsCommand
- system.NewInspectCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-19 15:17:41 +02:00
1d571d178d docs: deprecated: fix formatting of deprecated/removed in
- Use sentence-case to follow our docs guidelines.
- Add newlines to prevent these being rendered on a
  single line.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-19 15:04:53 +02:00
2e3d021912 Merge pull request #6263 from akerouanton/deprecate-legacy-links-env-vars
docs/deprecated: legacy links env vars
2025-08-19 15:01:18 +02:00
13010ba673 cli/registry/client: deprecate and move internal
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-19 14:57:00 +02:00
5c76f7f2d8 docs/deprecated: legacy links env vars
Signed-off-by: Albin Kerouanton <albinker@gmail.com>
Co-authored-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-19 14:45:53 +02:00
c2a042e0ed Merge pull request #6301 from Benehiko/commands/image
Unexport image commands
2025-08-19 14:37:12 +02:00
e66a1456d3 Unexport image commands
This patch deprecates exported image commands and moves the
implementation details to an unexported function.

Commands that are affected include:

- image.NewBuildCommand
- image.NewPullCommand
- image.NewPushCommand
- image.NewImagesCommand
- image.NewImageCommand
- image.NewHistoryCommand
- image.NewImportCommand
- image.NewLoadCommand
- image.NewRemoveCommand
- image.NewSaveCommand
- image.NewTagCommand
- image.NewPruneCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-19 14:25:15 +02:00
fcb260df1b Merge pull request #6299 from Benehiko/commands/container
Unexport container commands
2025-08-19 13:03:55 +02:00
6e5ce4df55 Merge pull request #6300 from thaJeztah/rm_decodeauthconfig
cli/command: TestRetrieveAuthTokenFromImage: don't decode authconfig
2025-08-19 12:48:50 +02:00
ae1727c41e cli/command: TestRetrieveAuthTokenFromImage: don't decode authconfig
Rewrite the test to not depend on registry.DecodeAuthConfig, which
may be moved internal to the daemon as part of the modules transition.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-19 12:39:10 +02:00
38595fecb6 Unexport container commands
This patch deprecates exported container commands and moves the
implementation details to an unexported function.

Commands that are affected include:
- container.NewRunCommand
- container.NewExecCommand
- container.NewPsCommand
- container.NewContainerCommand
- container.NewAttachCommand
- container.NewCommitCommand
- container.NewCopyCommand
- container.NewCreateCommand
- container.NewDiffCommand
- container.NewExportCommand
- container.NewKillCommand
- container.NewLogsCommand
- container.NewPauseCommand
- container.NewPortCommand
- container.NewRenameCommand
- container.NewRestartCommand
- container.NewRmCommand
- container.NewStartCommand
- container.NewStatsCommand
- container.NewStopCommand
- container.NewTopCommand
- container.NewUnpauseCommand
- container.NewUpdateCommand
- container.NewWaitCommand
- container.NewPruneCommand

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-19 11:12:19 +02:00
4c3fa4ac3c Merge pull request #6286 from Benehiko/commands/config
Unexport config command
2025-08-19 10:28:52 +02:00
6ad1d4617a Merge pull request #6285 from Benehiko/commands/checkpoint
Unexport checkpoint command
2025-08-19 10:27:49 +02:00
2d979220cc Merge pull request #6284 from Benehiko/commands/builder
Unexport the builder and bake stub command
2025-08-19 10:26:25 +02:00
cce29da061 Unexport config command
This patch unexports the `config` command.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-19 08:49:31 +02:00
3265cead1d Unexport checkpoint command
This patch unexports the `checkpoint` command.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-19 08:49:05 +02:00
1b9d0762a5 Unexport the builder command and bake stub command
This patch unexports the `builder` and `bake` stub command and it adds
deprecation notices on the exported functions.

It also registers the commands using the new `cli/internal/commands`
package when the init function executes.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-19 08:48:30 +02:00
6dcf9ac843 Merge pull request #6297 from thaJeztah/plugin_manager_more_deprecations
cli-plugins/manager: deprecate annotation metadata aliases
2025-08-18 20:30:02 -07:00
72f76f2720 cli-plugins/manager: deprecate annotation metadata aliases
These aliases were added in 292713c887
(part of v28.0), but did not deprecate them. They are no longer used
in the CLI itself, but may be used by cli-plugin implementations.

This deprecates the aliases in `cli-plugins/manager` in favor of
their equivalent in `cli-plugins/manager/metadata`:

- `CommandAnnotationPlugin`
- `CommandAnnotationPluginVendor`
- `CommandAnnotationPluginVersion`
- `CommandAnnotationPluginInvalid`
- `CommandAnnotationPluginCommandPath`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-18 21:50:53 +02:00
f9777d2517 cli: remove deprecated VisitAll, DisableFlagsInUseLine utilities
These were deprecated in 6bd8a4b2b5, and
are no longer used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-18 21:35:05 +02:00
5934553198 opts: remove deprecated NewNamedListOptsRef, NewNamedMapOpts
These were deprecated in 6f0c66c152 and are
no longer used.

This removes the deprecated:

- `NewNamedListOptsRef`
- `NewNamedMapOpts`
- `NamedListOpts`
- `NamedMapOpts`
- `NamedOption`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-18 18:55:54 +02:00
a056cc6164 opts: remove deprecated ListOpts.GetAll
It's no longer used and replaced by `ListOpts.GetSlice`. It was deprecated
in 5215b1eca4

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-18 18:55:50 +02:00
15f3e910d1 opts: remove deprecated ValidateHost
This function is no longer used, and was deprecated in
d0ac0acff0.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-18 18:40:05 +02:00
0c07d81a03 opts: remove deprecated QuotedString
This type is no longer used, and was deprecated in
187a942a88

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-18 18:37:15 +02:00
e0af501e30 Merge pull request #6288 from thaJeztah/deprecate_opts
opts: deprecate NewNamedListOptsRef, NewNamedMapOpts
2025-08-18 18:20:04 +02:00
5f5a5e1297 Merge pull request #6290 from thaJeztah/deprecate_quoted_values
Deprecate special handling for quoted values for TLS flags
2025-08-18 18:16:56 +02:00
ee05a71513 Deprecate special handling for quoted values for TLS flags
The `--tlscacert`, `--tlscert`, and `--tlskey` command-line flags had
non-standard behavior for handling values contained in quotes (`"` or `'`).
Normally, quotes are handled by the shell, for example, in the following
example, the shell takes care of handling quotes before passing the values
to the `docker` CLI:

    docker --some-option "some-value-in-quotes" ...

However, when passing values using an equal sign (`=`), this may not happen
and values may be handled including quotes;

    docker --some-option="some-value-in-quotes" ...

This caused issues with "Docker Machine", which used this format as part
of its `docker-machine config` output, and the CLI carried special, non-standard
handling for these flags.

Docker Machine reached EOL, and this special handling made the processing
of flag values inconsistent with other flags used, so this behavior is
deprecated. Users depending on this behavior are recommended to specify
the quoted values using a space between the flag and its value, as illustrated
above.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-18 17:59:16 +02:00
6f0c66c152 opts: deprecate NewNamedListOptsRef, NewNamedMapOpts
The `NewNamedListOptsRef`, `NewNamedMapOpts` and related `NamedListOpts`,
`NamedMapOpts`, and `NamedOption` interface were added in [moby@677a6b3],
which added support for a `daemon.json` configuration file. That change
required a way to correlate command-line flags with their corresponding
fields in the `daemon.json` to detect conflicting options. At the time,
the CLI and daemon were produced from the same code, and shared packages
for command-line options, but when the CLI was moved to a separate
repository, these options were inherited.

[moby@677a6b3]: 677a6b3506

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-18 17:27:02 +02:00
5f6dfb00d8 Merge pull request #6287 from thaJeztah/progress_test
cli/command/container: TestRunPullTermination: rewrite with streamformatter
2025-08-18 17:07:32 +02:00
c06074b22f Merge pull request #6283 from Benehiko/command/registration
Add command registration helpers
2025-08-18 15:02:15 +00:00
4ead8784d0 Add command registration helpers
This patch adds helper methods to the CLI to register cobra commands.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-08-18 16:56:35 +02:00
69854c4e08 cli/command/container: TestRunPullTermination: rewrite with streamformatter
This makes the test slightly closer to the actual code in the daemon producing
the progress response;
cd844fd0b2/daemon/images/image_pull.go (L58-L70)
cd844fd0b2/daemon/internal/distribution/utils/progress.go (L14-L34)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-18 15:21:14 +02:00
877a6ef29f Merge pull request #6273 from thaJeztah/cli_internalize_utils
cli: deprecate VisitAll, DisableFlagsInUseLine utilities, remove HasCompletionArg
2025-08-18 11:40:47 +02:00
2bcf433605 Merge pull request #6281 from thaJeztah/hosts_regular_stringarray
cli/flags: add "hostVar" to handle --host / -H as a single string
2025-08-18 11:24:10 +02:00
f14eeeb361 cli/flags: add "hostVar" to handle --host / -H as a single string
hostVar is used for the '--host' / '-H' flag to set [ClientOptions.Hosts].
The [ClientOptions.Hosts] field is a slice because it was originally shared
with the daemon config. However, the CLI only allows for a single host to
be specified.

hostVar presents itself as a "string", but stores the value in a string
slice. It produces an error when trying to set multiple values, matching
the check in [getServerHost].

[getServerHost]: 7eab668982/cli/command/cli.go (L542-L551)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-18 11:01:39 +02:00
5ee2906e78 cli/flags: use a regular StringArray for the --host / -H flag
The ClientOptions struct and related flags were inherited from the Moby
repository, where originally the CLI and Daemon used the same implementation
and had a "Common" options struct. When the CLI moved to a separate repository,
those structs were duplicated, but some daemon-specific logic remained. For
example, the daemon can be configured to listen on multiple ports and sockets
([moby@dede158]), but the CLI [can only connect to a single host][1]. The
daemon config also had to account for flags conflicting with `daemon.json`,
and use special flag-vars for this ([moby@677a6b3]).

Unfortunately, the `ClientConfig` struct became part of the public API and
is used as argument in various places, but we can remove the use of the
special flag var. This patch replaces the use of `NewNamedListOptsRef`
for a regular `StringArray`.

Unfortunately this changes the flag's type description from `list` to
`stringArray`, but we can look at changing that separately.

[moby@dede158]: dede1585ee
[1]: 0af135e906/docker/docker.go (L191-L193)
[moby@677a6b3]: 677a6b3506

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-16 19:13:27 +02:00
7eab668982 Merge pull request #6279 from thaJeztah/deprecate_ValidateHost
opts: deprecate ValidateHost utility
2025-08-16 07:21:40 -07:00
ccc1c65859 Merge pull request #6270 from thaJeztah/fix_prune_example
docs: fix output example for docker system prune
2025-08-16 07:20:13 -07:00
842f8beb00 Merge pull request #6274 from thaJeztah/deprecate_quotedstring
opts: deprecate QuotedString
2025-08-15 13:19:26 -07:00
d0ac0acff0 opts: deprecate ValidateHost utility
The `ValidateHost` option was introduced in [moby@1ba1138] to be used
as validation func for the `--host` flag on the daemon in and CLI in
[moby@5e3f6e7], but is no longer used since [cli@6f61cf0]. which added
support for `ssh://` connections, and required validation elsewhere.

[moby@1ba1138]: 1ba11384bf
[moby@5e3f6e7]: 5e3f6e7023
[cli@6f61cf0]: 6f61cf053a

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-15 22:16:11 +02:00
187a942a88 opts: deprecate QuotedString
The `QuotedString` option was added in [moby@e4c1f07] and [moby@abe32de]
to work around a regression in Docker 1.13 that caused `docker-machine`
to fail. `docker-machine` produced instructions on how to set up a cli
to connect to the Machine it produced. These instructions used quotes
around the paths for TLS certificates, but with an `=` for the flag's
values instead of a space; due to this the shell would not handle
stripping quotes, so the CLI would now get the value including quotes.

Preserving quotes in such cases is expected (and standard behavior), but
versions of Docker before 1.13 used a custom "mflag" package for flag
parsing, and that package contained custom handling for quotes (added
in [moby@0e9c40e]).

For other flags, this problem could be solved by the user, but as these
instructions were produced by `docker-machine`'s `config` command, an
exception was made for the `--tls-xxx` flags. From [moby-29761]:

> The flag trimming behaviour is really unusual, and I would say unexpected.
> I think removing it is generally the right idea. Since we have one very
> common case where it's necessary for backwards compatibility we need to
> add a special case, but I don't think we should apply that case to every
> flag.

The `QuotedString` implementation has various limitations, as it doesn't
follow the same handling of quotes as a shell would.

Given that Docker Machine reached EOL a long time ago and other options,
such as `docker context`, have been added to configure the CLI to connect
to a specific host (with corresponding TLS configuration), we should remove
the special handling for these flags, as it's inconsitent with all other
flags, and not worth maintaining for a tool that no longer exists.

This patch deprecates the `QuotedString` option and removes its use. A
temporary, non-exported copy is added, but will be removed in the next
release.

[moby-29761]: https://github.com/moby/moby/issues/29761#issuecomment-270211265
[moby@e4c1f07]: e4c1f07729
[moby@abe32de]: abe32de6b4
[moby@0e9c40e]: 0e9c40eb82
[moby@c79a169]: c79a169a35

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-15 18:58:09 +02:00
5a38118956 cmd/docker: fix some minor linting issues
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-15 17:45:26 +02:00
6bd8a4b2b5 cli: deprecate VisitAll, DisableFlagsInUseLine utilities
These utilities were only used internally; create a local copy
where used, and deprecate the ones in cli.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-15 17:45:21 +02:00
5a99022556 cli: remove HasCompletionArg utility
It was only used in a single place and has no external consumers.
Move it to where it's used to keep things together.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-15 14:21:47 +02:00
bf13010df8 docs: fix output example for docker system prune
The example shows that the `--volumes` option is used, which in current
versions of docker only removes "anonymous" volumes, but preserves named
volume:

    $ docker system prune -a --volumes
    ...
            - all anonymous volumes not used by at least one container
    ...

But the example output showed that a named volume ("named-vol") was
deleted;

    Deleted Volumes:
    named-vol

Co-authored-by: Roberto Villarreal <rrjjvv@yahoo.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-15 12:55:03 +02:00
53b6fddced Merge pull request #6261 from vvoland/gha-fix
gha/validate-pr: Replace `continue-on-error`
2025-08-14 14:17:43 +02:00
4cd9833d7c gha/validate-pr: Replace continue-on-error
The label validation steps now properly fail the workflow when required
labels are missing, instead of continuing with errors.

This change removes the `continue-on-error: true` directives and adds
`always()` conditions to ensure the validation steps run regardless of
previous step outcomes.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-08-14 12:50:00 +02:00
4bff12f476 Merge pull request #6260 from vvoland/gha-changelog-kind
.github/workflows: Add kind label validation to PR workflow
2025-08-14 11:25:41 +02:00
1456b53e4e .github/workflows: Add kind label validation to PR workflow
The PR validation workflow now enforces that every PR with an 'impact/*'
label must also have a corresponding 'kind/*' label, in addition to the
existing 'area/*' label requirement.

This change helps ensure proper categorization of pull requests by
requiring contributors to specify both the impact area and the kind of
change being made.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-08-14 10:34:07 +02:00
6d9b06d227 gha/validate-pr: Run on synchronize
Align with moby/moby

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-08-14 10:33:59 +02:00
e8876edcc2 Merge pull request #6254 from thaJeztah/cp_cleanup
cli/command/container: copyToContainer: improve error-handling
2025-08-13 11:22:30 +02:00
eb5b03a8a3 cli/command/container: copyToContainer: improve error-handling
The logic used in this function was confusing; some errors were ignored,
but responses handled regardless. The intent here is to try to detect
whether the destination exists inside the container and is of the right
"type" (otherwise produce an error).

Failing to "stat" the path in the container means we can't produce a
nice error for the user, but we'll continue the request, which either
would succeed or produce an error returned by the daemon.

While working on this patch, I noticed that some error-handling on the
daemon side is incorrect. This patch does not fix those cases, but
makes the logic slightly easier to follow (we should consider extracting
the "stat" code to a separate function though).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-13 00:24:31 +02:00
b71a055a11 Merge pull request #6253 from docker/dependabot/github_actions/actions/checkout-5
build(deps): bump actions/checkout from 4 to 5
2025-08-13 00:14:50 +02:00
c5ea9079af cli/command/container: copyToContainer rename error-return
Make it more clearly identifiable where we're dealing with the
named error-return

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-12 18:53:12 +02:00
f2af519f2e build(deps): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 16:52:05 +00:00
bd0546ad5b Merge pull request #6252 from thaJeztah/less_pkg_errors
reduce uses of pkg/errors
2025-08-12 07:44:16 +02:00
27a7947535 cli/command/image/build/internal/git: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:09 +02:00
53183396d7 internal/volumespec: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:08 +02:00
70f1147394 cli/command/trust: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:08 +02:00
a8f11a2fa2 cli/command/formatter: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:08 +02:00
c612e141b5 cli/command/registry: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:08 +02:00
9b7ee0e201 cli/config: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:08 +02:00
3b677449d8 cli/context: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:08 +02:00
d38317c781 cli/compose: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:07 +02:00
2dd462cc36 cli/command/idresolver: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:07 +02:00
4c89455378 cli/registry/client: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:07 +02:00
adbe04b5fc cli/manifest, cli/command/manifest: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:07 +02:00
097cc9ca64 cli/trust: use stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:07 +02:00
e069ded4c3 cli: reduce uses of pkg/errors for stdlib errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 19:00:02 +02:00
44eba133d6 Merge pull request #6250 from thaJeztah/bump_go_connections
vendor: github.com/docker/go-connections v0.6.0
2025-08-11 15:56:03 +02:00
3529651fa7 vendor: github.com/docker/go-connections v0.6.0
- deprecate sockets.GetProxyEnv, sockets.DialerFromEnvironment
- add support for unix sockets on Windows
- remove legacy CBC cipher suites from client config
- align client and server defaults to be the same.
- remove support for encrypted TLS private keys.
- nat: optimize ParsePortSpec

full diff: https://github.com/docker/go-connections/compare/v0.5.0...v0.6.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-11 15:33:22 +02:00
8324b17f9a Merge pull request #6249 from thaJeztah/skip_TestPromptExitCode
e2d skip flaky test: TestPromptExitCode/plugin_upgrade, plugin_install
2025-08-11 15:06:56 +02:00
d16defd9e2 Merge pull request #6247 from austinvazquez/update-golang-1.24.6
update to go1.24.6
2025-08-08 19:56:53 +02:00
3035b6685b e2d skip flaky test: TestPromptExitCode/plugin_upgrade, plugin_install
This test was recently rewritten from testing plugin upgrade with
DCT enabled to just "plugin upgrade", but there's a fair amount of
complexity in the e2e tests that set up different daemons and registries.

It's possible that tests are affecting each-other, and some state (config)
is left behind.

Let's skip the test for now, and add a tracking ticket to dig deeper.

    === FAIL: e2e/global TestPromptExitCode/plugin_upgrade (7.55s)
        cli_test.go:205: assertion failed:
            Command:  docker plugin push registry:5000/plugin-upgrade-test:latest
            ExitCode: 1
            Error:    exit status 1
            Stdout:   The push refers to repository [registry:5000/plugin-upgrade-test]
            459089aa5943: Preparing
            adc41078d1d9: Preparing
            d7bff979db13: Preparing
            459089aa5943: Preparing

            Stderr:   error pushing plugin: failed to do request: Head "https://registry:5000/v2/plugin-upgrade-test/blobs/sha256:adc41078d1d937495df2f90444e5414a01db31e5a080f8aa4f163c64d41abd11": http: server gave HTTP response to HTTPS client

            Failures:
            ExitCode was 1 expected 0
            Expected no error

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-08 19:07:00 +02:00
6769f62746 update to go1.24.6
- https://github.com/golang/go/issues?q=milestone%3AGo1.24.6+label%3ACherryPickApproved
- full diff: golang/go@go1.24.5...go1.24.6

These minor releases include 2 security fixes following the security policy:

- os/exec: LookPath may return unexpected paths

If the PATH environment variable contains paths which are executables (rather
than just directories), passing certain strings to LookPath ("", ".", and ".."),
can result in the binaries listed in the PATH being unexpectedly returned.

Thanks to Olivier Mengué for reporting this issue.

This is CVE-2025-47906 and Go issue https://go.dev/issue/74466.

- database/sql: incorrect results returned from Rows.Scan

Cancelling a query (e.g. by cancelling the context passed to one of the query
methods) during a call to the Scan method of the returned Rows can result in
unexpected results if other queries are being made in parallel. This can result
in a race condition that may overwrite the expected results with those of
another query, causing the call to Scan to return either unexpected results
from the other query or an error.

We believe this affects most database/sql drivers.

Thanks to Spike Curtis from Coder for reporting this issue.

This is CVE-2025-47907 and https://go.dev/issue/74831.

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

Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
2025-08-08 10:15:50 -05:00
ef38d81fdb Merge pull request #6245 from thaJeztah/rm_completion
cli/command/completion: remove deprecated ValidArgsFn
2025-08-07 13:52:04 +02:00
5052a39915 cli/command/completion: remove deprecated ValidArgsFn
This was deprecated in 9f19820f88, which
is part of v28.x, and unlikely used externally.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-06 23:53:56 +02:00
4beddd3e25 Merge pull request #6244 from thaJeztah/remove_trust_flag_helpers
cli/command: remove `AddTrustSigningFlags`, `AddTrustVerificationFlags`, `AddPlatformFlag` utilities
2025-08-06 19:37:36 +02:00
7026e68a71 cli/command: remove AddPlatformFlag utility
It was only used internally and has no external users. It should not be
used for new uses, because it also adds a minimum API version constraint
and a default from env-var, which must be evaluated for each individual
use of such flags.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-06 17:15:32 +02:00
c0fbbe05ca cli/command: remove AddTrustVerificationFlags
It was only used internally; inline it where used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-06 17:00:25 +02:00
8c22927978 cli/command: remove AddTrustSigningFlags
it was only used internally in a single location, so inline the
code where it's used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-06 16:55:42 +02:00
c1cc6b61a3 Merge pull request #6233 from thaJeztah/plugin_no_dct
cli/command/plugin: remove DCT
2025-08-06 14:04:01 +02:00
3f5b1bdd32 cli/command/plugin: remove DCT
Plugins are not widely used, and there's no known plugins that use
content-trust. We're working on updating the authentication stack
in the CLI, and the trust implementation hinders us in making
changes, so removing parts that are not high-priority (ahead of
full deprecation of DCT).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-06 12:33:27 +02:00
845870e669 Merge pull request #6243 from thaJeztah/remove_prompt_utils
cli/command: remove prompt utilities that were for internal use
2025-08-06 12:30:31 +02:00
8683664b29 Merge pull request #6238 from thaJeztah/e2e_touchups
e2e: minor cleanups in `TestPromptExitCode`
2025-08-06 12:30:08 +02:00
d3c23a223c e2e/global: TestPromptExitCode: group plugin preparation steps
Use names for the plugin that don't refer to content-trust, as that's
not related to this test.

Make it slightly more clear which steps are preparation and which
are the actual test. The test sometimes fails in the preparation
step, and we could consider moving those separate and XFail the
test if the preparation fails;

        Stderr:   error pushing plugin: failed to do request: Head "https://registry:5000/v2/plugin-content-trust-upgrade/blobs/sha256:af932a31d4df3a2890f900bcf28e16cea87b2b440b8036ba86ab3418f3e50a35": http: server gave HTTP response to HTTPS client

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-06 10:57:01 +02:00
081add2fc5 e2e/testutils: SetupPlugin: return path of directory
The gotest.tools `fs.NewDir` utility already sets up a `t.Cleanup`,
so we can treat it the same as `t.TempDir()` and let it handle
cleaning up by itself.

We should probably consider replacing some of this with `t.TempDir`.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-06 10:57:01 +02:00
8972e53ad0 cli/command: remove prompt utilities that were for internal use
- The `DisableInputEcho` and `PromptForInput` utilities were added in
  c15ade0c64 as part of a bug-fix, which
  was part of v28.x. [There are no (publicly visible) users][1] of either.
- The `ErrPromptTerminated` was added in v26.x (originally added in
  10bf91a02d, later updated in commit
  7c722c08d0. [It is not used][2]
- The `PromptForConfirmation` was added in [moby@280c872] (docker v1.13.0)
  as part of the `docker <object> prune` subcommands. It was meant for
  internal use but exported to allow re-using it in the `container`,
  `image` (etc.) packages. However, a breaking change to its signature
  was made in 10bf91a02d. It currently
  does [not appear to have any (public) users][2].

This patch removes the `ErrPromptTerminated`, `DisableInputEcho`,
`PromptForInput`, and `PromptForConfirmation` utilities from the
`cli/command` package. The core functionality of these is still
available in the `internal/prompt` package, which we may make
public at some point, but still needs some refining / decoupling.

[moby@280c872]: 280c872366
[1]: https://grep.app/search?f.lang=Go&regexp=true&q=%5C.%28DisableInputEcho%7CPromptForInput%29%5C%28
[2]: https://grep.app/search?f.lang=Go&q=%5C.ErrPromptTerminated
[3]: https://grep.app/search?f.lang=Go&q=.PromptForConfirmation%28

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-06 10:50:31 +02:00
f2c64c123f Merge pull request #6241 from thaJeztah/deprecate_bind_nonrecursive
remove deprecated `bind-nonrecursive` option for `--mount`
2025-08-06 10:48:58 +02:00
25f95877b5 Merge pull request #6219 from thaJeztah/cleanup_credentialSpecOpt
cli/command/service: credentialSpecOpt: use strings.Cut
2025-08-06 10:09:26 +02:00
14ed619736 Merge pull request #6240 from thaJeztah/remove_deprecated
cli/command: remove deprecated CopyToFile, ConfigureAuth utilities
2025-08-06 10:08:33 +02:00
7dd9c20cac Merge pull request #6235 from thaJeztah/remove_cli_experimental_remnants
remove some remnants from CLI "experimental" config option
2025-08-06 09:50:27 +02:00
39829affbe Merge pull request #6242 from thaJeztah/bump_mergo
vendor: dario.cat/mergo v1.0.2
2025-08-06 07:33:22 +00:00
a93ed48d06 vendor: dario.cat/mergo v1.0.2
drops gopkg.in/yaml.v3 as dependency

full diff: https://github.com/darccio/mergo/compare/v1.0.1...v1.0.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-06 01:16:17 +02:00
f1ceb8c55d Merge pull request #6236 from thaJeztah/system_prune_register
system prune: refactor to use "register" functions
2025-08-05 23:25:51 +02:00
abfe4d4629 remove deprecated bind-nonrecursive option for --mount
The `bind-nonrecursive` option was replaced with the [`bind-recursive`]
option (see [cli-4316], [cli-4671]). The option was still accepted, but
printed a deprecation warning:

    bind-nonrecursive is deprecated, use bind-recursive=disabled instead

In the v29.0 release, this warning is removed, and returned as an error.
Users should use the equivalent `bind-recursive=disabled` option instead.

[`bind-recursive`]: https://docs.docker.com/engine/storage/bind-mounts/#recursive-mounts
[cli-4316]: https://github.com/docker/cli/pull/4316
[cli-4671]: https://github.com/docker/cli/pull/4671

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-05 23:23:38 +02:00
68fc942fd2 Merge pull request #6232 from thaJeztah/bump_engine
vendor: moby/moby/api v1.52.0-alpha.1, moby/moby/client v0.1.0-alpha.0
2025-08-05 22:42:19 +02:00
f9431e3b35 vendor: moby/moby/api v1.52.0-alpha.1, moby/moby/client v0.1.0-alpha.0
full diff: 4faedf2bec...37d0204d7f

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-05 22:01:50 +02:00
22cc0e90ae cli/command: remove deprecated ConfigureAuth utility
It was deprecated in 6e4818e7d6, which
is part of v28.x and backported to v27.x.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-05 21:22:59 +02:00
de54347518 cli/command: remove deprecated CopyToFile utility
It was deprecated in 7cc6b8ebf4, which is
part of v28.x

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-05 21:11:47 +02:00
b01d359cc9 Merge pull request #6239 from thaJeztah/no_pkg_process
cli/connhelper: remove dependency on pkg/process
2025-08-05 16:57:54 +02:00
2abcbf842f cli/connhelper: remove dependency on pkg/process
This package will not be included in the api or client modules, and
we're currently only using a single function of it, and only the
unix implementation, so let's fork it for now (although the package
may be moved to moby/sys).

This removes the last dependency on github.com/docker/docker.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-05 16:30:09 +02:00
fcfaa8daeb Merge pull request #6234 from thaJeztah/ParseRepositoryInfo_no_err_step2
internal/registry: remove RepositoryInfo, add NewIndexInfo
2025-08-04 15:21:49 +02:00
a629a840a8 Merge pull request #6237 from thaJeztah/plugin_manager_unexport
cli-plugins/manager: various fixes and deprecations
2025-08-04 14:00:21 +02:00
513ceeec0a cli-plugins/manager: remove deprecated ResourceAttributesEnvvar
This const was deprecated in 9dc175d6ef,
which is part of v28.0, so let's remove it.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 12:26:22 +02:00
5876b2941c cli-plugins/manager: deprecate metadata aliases
These aliases were added in 4321293972
(part of v28.0), but did not deprecate them. They are no longer used
in the CLI itself, but may be used by cli-plugin implementations.

This deprecates the aliases in `cli-plugins/manager` in favor of
their equivalent in `cli-plugins/manager/metadata`:

- `NamePrefix`
- `MetadataSubcommandName`
- `HookSubcommandName`
- `Metadata`
- `ReexecEnvvar`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 12:26:22 +02:00
50963accec cli-plugins/manager: wrapAsPluginError: don't special-case nil
This was a pattern inheritted from pkg/errors.Wrapf, which ignored
nil errors for convenience. However, it is error-prone, as it is
not obvious when returning a nil-error.

All call-sites using `wrapAsPluginError` already do a check for
nil errors, so remove this code to prevent hard to find bugs.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 12:26:14 +02:00
d789bac04a cli-plugins/manager: pluginError: remove Causer interface
We no longer depend on this interface and it implements Unwrap for
native handling by go stdlib.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 11:11:52 +02:00
71460215d3 cli-plugins/manager: deprecate "IsNotFound"
These errors satisfy errdefs.IsNotFound, so make it a wrapper, and
deprecate it.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 11:06:49 +02:00
1cc698c68f cli-plugins/manager: un-export "NewPluginError"
It is for internal use, and no longer needed for testing, now that
the `Plugin` type handles marshalling errors.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 11:01:24 +02:00
549d39a89f cli-plugins/manager: fix Plugin marshaling with regular errors
Go does not by default marshal `error` type fields to JSON. The manager
package therefore implemented a `pluginError` type that implements
[encoding.TextMarshaler]. However, the field was marked as a regular
`error`, which made it brittle; assining any other type of error would
result in the error being discarded in the marshaled JSON (as used in
`docker info` output), resulting in the error being marshaled as `{}`.

This patch adds a custom `MarshalJSON()` on the `Plugin` type itself
so that any error is rendered. It checks if the error used already
implements [encoding.TextMarshaler], otherwise wraps the error in
a `pluginError`.

[encoding.TextMarshaler]: https://pkg.go.dev/encoding#TextMarshaler

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 10:55:16 +02:00
54367b3283 cli-plugins/manager: un-export "Candidate" interface
It is for internal use for mocking purposes, and is not part
of any public interface / signature.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 09:14:13 +02:00
057f3128b6 cli-plugins/manager: reformat TestValidateCandidate table
Slightly more verbose, but makes it easier to see properties
of each test.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 08:50:07 +02:00
dfbac70efa remove some remnants from CLI "experimental" config option
Experimental is always enabled (977d3ae046),
and the `Experimental` field in plugin metadata was deprecated in
977d3ae046 and removed in commit
6a50c4f700.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 08:48:29 +02:00
3b6a556533 cli/command: remove exported "RunPrune" functions
These are no longer used, and unlikely to be used externally.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 00:08:03 +02:00
bf8cb43025 system prune: delegate version check
Move the version-check for pruners to the pruner, which can
return a [ErrNotImplemented] error to indicate they won't
be run with the API version that's used.

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

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 00:08:02 +02:00
a888c4091c system prune: delegate confirmation message and validation
This adds a "dry-run" / "pre-check" option for prune-functions,
which delegates constructing the confirmation message (what is
about to be pruned) and validation of the given options to the
prune-functions.

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-04 00:07:54 +02:00
02d578b637 system prune: use register function for prune functions
Introduce a "prune" package in which we maintain a list of prune
functions that are registered. Known prune "content-types" are
included in a pre-defined order, after which additional content
can be registered.

Using this approach no longer requires the "RunPrune" functions
to be exported, and allows additional content-types to be
introduced without having to import those packages into the
system package, so keeping things more decoupled.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-03 23:52:47 +02:00
21e8bbc8a2 internal/registry: remove RepositoryInfo, add NewIndexInfo
Most places only use IndexInfo (and may not even need that), so replace
the use of ParseRepositoryInfo for NewIndexInfo, and move the RepositoryInfo
type to the trust package, which uses it as part of its ImageRefAndAuth
struct.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-03 15:32:51 +02:00
f86ad2ea4c Merge pull request #6228 from thaJeztah/bump_version
bump version to v29.0.0-dev
2025-07-31 18:28:28 +02:00
a4bf9e78e5 Merge pull request #6227 from thaJeztah/cleanup_swarmopts
opts/swarmopts: minor cleanup and refactor
2025-07-31 18:28:07 +02:00
a1ea79444b Merge pull request #6230 from robmry/moby29_dockerd_reference
dockerd.md: --firewall-backend and --bridge-accept-fwmark
2025-07-31 18:27:03 +02:00
066710ba7b opts/swarmopts: minor cleanup and refactor
- Use strong-typed switches for validating options
- Initialize defaults instead of setting them after
  parsing the ports. Each option should be validated
  as part of the parsing, so no invalid (or empty)
  values should be set.
- Put variables closer to where they're used, and
  pre-allocate slices.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-31 18:19:58 +02:00
b8df4abeb5 bump version to v29.0.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-07-31 18:17:29 +02:00
3f0ccd1b71 dockerd.md: Add --firewall-backend
Related to 39ab393274

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-07-31 17:11:47 +01:00
6176a7686e dockerd.md: add --bridge-accept-fwmark
Related to cf1695bef1

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-07-31 17:11:47 +01:00
66aca29f7d Merge pull request #6226 from thaJeztah/bump_engine
vendor: docker/docker, moby/moby/api and moby/moby/client 4faedf2bec36
2025-07-31 18:10:08 +02:00
f937e62c89 replace direct uses of github.com/docker/go-connections/nat types
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-31 17:37:34 +02:00
bf16dd1251 vendor: docker/docker, moby/moby/api and moby/moby/client 4faedf2bec36
notable changes:

- api: remove deprecated NoBaseImageSpecifier
- api/stdcopy: move to api/pkg/stdcopy
- api/types/container: add aliases for go-connections/nat types
- pkg/progress: move to api/pkg/progress
- pkg/jsonmessage: move JSONError to api/types/jsonstream
- pkg/jsonmessage: move JSONProgress to api/types/jsonstream
- pkg/jsonmessage: move to client/pkg/jsonmessage
- pkg/jsonmessage: remove github.com/morikuni/aec dependency
- pkg/jsonmessage: stop printing deprecated progressDetail, errorDetail,
  remove DisplayJSONMessagesToStream and Stream interface
- pkg/streamformatter: move to api/pkg/streamformatter
- pkg/streamformatter: split from pkg/jsonmessage

full diff: 2574c2b2e9...4faedf2bec

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-31 17:31:01 +02:00
2199a05e08 Merge pull request #6212 from carsontham/e2e-test-container-rename
intergration-cli: migrate TestContainerAPIRename to cli e2e test
2025-07-29 20:17:38 +02:00
149503a32c migrate e2e container rename test
Signed-off-by: carsontham <carsontham@outlook.com>
2025-07-29 20:07:15 +02:00
5c3577ff9f cli/command/service: credentialSpecOpt: use strings.Cut
- Rewrite the function to use strings.Cut instead of checking for,
  and trimming prefixes for each option.
- More explicitly set the value, instead of setting an empty value,
  then propagating the struct.
- Define a "type" to provide a more enum-like construct.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-29 19:55:50 +02:00
14203bbc77 Merge pull request #6216 from thaJeztah/bump_engine
vendor: docker/docker, moby/moby/api and moby/moby/client 2574c2b2e917
2025-07-29 19:51:52 +02:00
b6d7ac34be vendor: docker/docker, moby/moby/api and moby/moby/client 2574c2b2e917
notable changes;

- api/types/container: move StateStatus, NewStateStatus internal again
- daemon/server/httputils: remove ContainerDecoder interface
- runconfig: move to daemon/internal/runconfig
- pkg/stack: move to daemon/internal
- remove pkg/stringid as it has moved to the client module
- remove pkg/stdcopy as it has moved to the api module
- pkg/rootless: move to daemon/internal
- move api/types/plugins/logdriver to daemon/internal
- move api/types/plugins/logdriver to daemon/internal
- pkg/system: move to daemon/internal
- remove pkg/fileutils

full diff: 25e2b4d485...2574c2b2e9

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-29 18:56:56 +02:00
83e507377a vendor: docker/docker, moby/moby/api and moby/moby/client 25e2b4d48551
notable changes:

- api/types/container.StatsResponseReader: move to client
- api/types: move backend types to daemon/server
- runconfig: remove exported errors

full diff: c4afa77157...25e2b4d485

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-29 18:48:18 +02:00
a0ae6e6a5a Merge pull request #6215 from thaJeztah/ParseRepositoryInfo_no_err
internal/registry: ParseRepositoryInfo: remove unused error return
2025-07-29 18:41:18 +02:00
86b5b528a6 internal/registry: ParseRepositoryInfo: remove unused error return
Removed the error return from the `ParseRepositoryInfo` function.
There are no validation steps inside `ParseRepositoryInfo` which
could cause an error, so we always returned a nil error.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-29 18:32:15 +02:00
f0030712e9 Merge pull request #6217 from thaJeztah/errdefs_unalias
remove aliases for containerd/errdefs, disallow docker/errdefs
2025-07-28 20:12:39 +02:00
89d8c8a2a7 remove aliases for containerd/errdefs, disallow docker/errdefs
We transitioned most functionality of docker/errdefs to containerd
errdefs module, and the docker/errdefs package should no longer be
used.

Because of that, there will no longer be ambiguity, so we can remove
the aliases for this package, and use it as "errdefs".

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-28 14:55:43 +02:00
e2a4e429bc Merge pull request #6211 from thaJeztah/bump_engine
vendor: update docker, api, client to master, consume client/pkg/stringid
2025-07-28 14:25:36 +02:00
fdc4397906 Merge pull request #6214 from thaJeztah/template_deprecate_newparse
templates: deprecate NewParse()
2025-07-28 11:26:37 +02:00
d63cae6f1c cli/command/formatter: use alias/wrapper for TruncateID
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-28 08:50:15 +02:00
4bd6b6897f vendor: update docker, api, client to master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-28 08:50:13 +02:00
a1035b0796 Merge pull request #6213 from thaJeztah/cleanup_plugins
cli/command/plugin: fix linting issues, and assorted cleanups
2025-07-28 08:49:00 +02:00
7ab3e7e774 templates: deprecate NewParse()
It it just a chain of `New("sometag").Parse(...)`, and most of our
uses don't use a tag for the template, so can call Parse.

There's no public users of this function, but deprecating it first
just in case.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-28 08:25:02 +02:00
c6f935eba5 cli/command/plugin: fix linting issues, and assorted cleanups
- fix various unhandled errors
- remove some locally defined option-types in favor of option-types
  defined by the client / api
- don't use unkeyed structs in tests, and add docs for some subtests
- fix some values in tests that triggered "spellcheck" warnings
- inline vars / functions that only had a single use.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-26 16:45:04 +02:00
2f87a11e96 Merge pull request #6202 from Mewsen/issue/6188
refactor(cli/compose/loader): extract ParseVolume() to its own package
2025-07-25 17:08:54 +02:00
ef7fd8bb67 refactor(cli/compose/loader): extract ParseVolume() to its own package
Moves ParseVolume() to a new internal package to remove the dependency
on cli/compose/loader in cli/command/container/opts.go

refactor to keep types isolated

- rename the package to "volumespec" to reuse the name of the package
  as part of the name (parsevolume.ParseVolume() -> volumespec.Parse())
- move the related compose types to the internal package as well,
  and rename them to be more generic (not associated with "compose");
  - ServiceVolumeConfig -> VolumeConfig
  - ServiceVolumeBind -> BindOpts
  - ServiceVolumeVolume -> VolumeOpts
  - ServiceVolumeImage -> ImageOpts
  - ServiceVolumeTmpfs -> TmpFsOpts
  - ServiceVolumeCluster -> ClusterOpts
- alias the internal types inside cli/compose/types to keep backward
  compatibility (for any external consumers); even though the implementation
  is internal, Go allows aliasing types to use them externally.

Signed-off-by: Michael Tews <michael@tews.dev>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-25 16:56:17 +02:00
3046019d3b Merge pull request #6209 from vvoland/image-tree-nonexp
image/tree: Unmark as experimental, warn when redirected
2025-07-25 12:58:37 +02:00
1eeb0cc3e1 Merge pull request #6207 from thaJeztah/fork_registry
add internal fork of docker/docker/registry
2025-07-25 12:57:47 +02:00
9257cc7f68 image/tree: Unmark as experimental, warn when redirected
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-07-25 12:07:54 +02:00
f214f860b6 image/tree: Remove extra newline after legend
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-07-25 12:04:42 +02:00
f907c7a4b0 internal/registry: fix linting issues (revive)
internal/registry/errors.go:26:43: use-any: since Go 1.18 'interface{}' can be replaced by 'any' (revive)
    func invalidParamf(format string, args ...interface{}) error {
                                              ^
    internal/registry/registry_mock_test.go:52:51: use-any: since Go 1.18 'interface{}' can be replaced by 'any' (revive)
    func writeResponse(w http.ResponseWriter, message interface{}, code int) {
                                                      ^

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-25 00:08:26 +02:00
cd277a5815 cli/command/system: remove use of Mirrors field in test
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 23:29:48 +02:00
c297770d2d internal/registry: remove pkg/errors
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 23:29:47 +02:00
219cfc8b7d internal/registry: define local serviceConfig
The registry.ServiceConfig struct in the API types was meant for the
registry configuration on the daemon side; it has variuos fields we
don't use, defines methods for (un)marshaling JSON, and a custom version
of `net.IPNet`, also to (un)marshal JSON.

None of that is needed, so let's change it to a local type, and implement
a constructor (as we now only have "insecure registries" to care
about).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 23:29:47 +02:00
2607ba8062 internal/registry: remove ValidateIndexName
It was written to be used as validate-func for command-line flags, which
we don't use it for (which for CLI-flags includes normalizing the value).

The validation itself didn't add much; it only checked the registry didn't
start or end with a hyphen (which would still fail when parsing).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 23:29:36 +02:00
5322affc9f internal/registry: remove duplicate endpoint methods
now that we no longer need to account for mirrors, these were
identical, so just use a single one.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 23:23:11 +02:00
dc41365b56 internal/registry: remove NewStaticCredentialStore
It was only used in a single place; inline it there.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 23:23:10 +02:00
dad2e67860 internal/registry: remove PingResponseError
It's not matched anywhere, so we can just return a plain error.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 23:23:10 +02:00
7cf245d2f7 internal/registry: Service.Auth remove unused statusmessage return
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 23:23:10 +02:00
e0b351b3d9 internal/registry: remove code related to mirrors
The CLI does not have information about mirrors, and doesn't
configure them, so we can remove these parts.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 23:23:02 +02:00
7716219e17 internal/registry: remove dead code
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 22:14:25 +02:00
f6b90bc253 add internal fork of docker/docker/registry
This adds an internal fork of [github.com/docker/docker/registry], taken
at commit [moby@f651a5d]. Git history  was not preserved in this fork,
but can be found using the URLs provided.

This fork was created to remove the dependency on the "Moby" codebase,
and because the CLI only needs a subset of its features. The original
package was written specifically for use in the daemon code, and includes
functionality that cannot be used in the CLI.

[github.com/docker/docker/registry]: https://pkg.go.dev/github.com/docker/docker@v28.3.2+incompatible/registry
[moby@49306c6]: 49306c607b/registry

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 19:59:17 +02:00
636a4cf2dc Merge pull request #6208 from thaJeztah/bump_moby
vendor: github.com/docker/docker master
2025-07-24 19:45:06 +02:00
20181d4363 vendor: github.com/docker/docker master
forgot to update this dependency (only updated the api and client)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 18:12:28 +02:00
d29d719c42 Merge pull request #6204 from thaJeztah/bump_engine
vendor: docker/docker, moby/api, and moby/client master
2025-07-24 17:25:47 +02:00
fa169b6933 vendor: docker/docker, moby/api, and moby/client master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 16:06:08 +02:00
1ca6c946d5 Merge pull request #6126 from ctalledo/fix-for-moby-48759
Add support for multiple platform options in image load and save
2025-07-24 11:48:11 +02:00
c2586d68cf Merge pull request #6206 from thaJeztah/remove_RepoNameForReference
cli/registry/client: remove deprecated RepoNameForReference
2025-07-24 11:35:03 +02:00
a87bde0068 cli/registry/client: remove deprecated RepoNameForReference
This was deprecated in 6f46cd2f4b,
which is part of v28.x, and no longer used, so we can remove it.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-24 01:33:55 +02:00
df9950aa06 Merge pull request #6201 from thaJeztah/bump_engine
vendor: docker/docker, moby/api, moby/client to latest
2025-07-23 13:40:13 +02:00
323ef1997f vendor: docker/docker, moby/api, moby/client to latest
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-23 09:45:51 +02:00
80be02c72b Merge pull request #6200 from thaJeztah/search_no_registrypkg
cli/command/registry: remove uses of registry.ParseSearchIndexInfo
2025-07-22 23:01:28 +02:00
e504faf6da cli/command/registry: remove uses of registry.ParseSearchIndexInfo
This utility was only used in the CLI, but the implementation was
based on it being used on the daemon side, so included resolving
the host's IP-address, mirrors, etc.

The only reason it's used in the CLI is to provide credentials for
the registry that's being searched, so reduce it to just that.

There's more cleaning up to do in this area, so to make our lives
easier, it's implemented locally as non-exported functions; likely
to be replaced with something else.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-22 22:13:38 +02:00
03ff54b8ba Merge pull request #6193 from thaJeztah/bump_engine
vendor: github.com/docker/docker master (v29.0-dev)
2025-07-22 09:19:13 +02:00
644dc16b16 vendor: github.com/docker/docker master (v29.0-dev)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-21 23:04:50 +02:00
2a2748a94c Merge pull request #6195 from thaJeztah/build_no_dct
build: remove DCT support for classic builder
2025-07-21 22:03:46 +02:00
7609dde8d0 build: remove DCT support for classic builder
Docker Content Trust is currently only implemented for the classic
builder, but is known to not work with multi-stage builds, and
requires rewriting the Dockerfile, which is brittle because the
Dockerfile syntax evolved with the introduction of BuildKit as
default builder.

Given that the classic builder is deprecated, and only used for
Windows images, which are not verified by content trust;

    # docker pull --disable-content-trust=false mcr.microsoft.com/windows/servercore:ltsc2025
    Error: remote trust data does not exist for mcr.microsoft.com/windows/servercore: mcr.microsoft.com does not have trust data for mcr.microsoft.com/windows/servercore

With content trust not implemented in BuildKit, and not implemented
in docker compose, this resulted in an inconsistent behavior.

This patch removes content-trust support for "docker build". As this
is a client-side feature, users who require this feature can still
use an older CLI to to start the build.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-21 19:05:38 +02:00
71bc8ab3ea Merge pull request #6186 from thaJeztah/remove_more_aliases
opts: minor cleanup in tests, and remove some import aliases
2025-07-17 17:18:15 +02:00
6cf2c023f8 Merge pull request #6187 from thaJeztah/container_unexport
cli/command/container: deprecate NewDiffFormat, DiffFormatWrite
2025-07-17 17:09:50 +02:00
73604b8c36 Merge pull request #6192 from thaJeztah/trust_no_api_const
cli/command/image: remove use of api.NoBaseImageSpecifier
2025-07-17 15:59:06 +02:00
e2cab2c64c cli/command/image: remove use of api.NoBaseImageSpecifier
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-17 14:28:13 +02:00
74042f5ffa Merge pull request #6191 from robmry/info_firewall_backend
Include FirewallBackend in docker info output
2025-07-17 11:52:35 +02:00
8c317ad3fd Merge pull request #6190 from thaJeztah/fork_remotecontext
add local fork of github.com/docker/docker/builder/remotecontext
2025-07-17 01:34:55 +02:00
64f33cd463 TestCloneArgsSmartHttp: fix unhandled error
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 22:21:46 +02:00
a3bea24086 Include FirewallBackend in docker info output
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-07-16 17:17:27 +00:00
b05aa464a6 Dockerfile: install git-daemon for use in tests
gitutils_test.go:210: git-http-backend: git: 'http-backend' is not a git command. See 'git --help'.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 16:48:40 +02:00
e34616574f fix linting issues
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 16:27:27 +02:00
8d2ccc128a add local fork of github.com/docker/docker/builder/remotecontext
Adds a local fork of this package for use in the classic builder.

Code was taken at commit [d33d46d01656e1d9ee26743f0c0d7779f685dd4e][1].

Migration was done using the following steps:

    # install filter-repo (https://github.com/newren/git-filter-repo/blob/main/INSTALL.md)
    brew install git-filter-repo

    # create a temporary clone of docker
    cd ~/Projects
    git clone https://github.com/docker/docker.git build_context_temp
    cd build_context_temp

    # commit taken from
    git rev-parse --verify HEAD
    d33d46d01656e1d9ee26743f0c0d7779f685dd4e

    git filter-repo --analyze

    # remove all code, except for the remotecontext packages, and move to build/internal docs and previous locations of it
    git filter-repo \
      --path 'builder/remotecontext/git' \
      --path 'builder/remotecontext/urlutil' \
      --path-rename builder/remotecontext:cli/command/image/build/internal

    # go to the target repository
    cd ~/go/src/github.com/docker/cli

    # create a branch to work with
    git checkout -b fork_remotecontext

    # add the temporary repository as an upstream and make sure it's up-to-date
    git remote add build_context_temp ~/Projects/build_context_temp
    git fetch build_context_temp

    # merge the upstream code
    git merge --allow-unrelated-histories --signoff -S build_context_temp/master

[1]: https://github.com/docker/docker/d33d46d01656e1d9ee26743f0c0d7779f685dd4e

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 16:18:51 +02:00
b5a939268b Merge pull request #6189 from thaJeztah/cleanup_build_context
cli/command/image: move build-context detection to build
2025-07-16 15:57:58 +02:00
260f1dbebb cli/command/image: move build-context detection to build
Removes direct imports of github.com/docker/docker/builder in
the image package, to be moved later.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 15:50:32 +02:00
e95d133612 remove some redundant import aliases
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 13:56:49 +02:00
3dec3879c8 opts: minor cleanup in tests
- use consistent name for MountOpt vars
- cleanup some comments and make them a GoDoc
- remove import alias
- use subtests for tests that were prepared for it.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 13:56:49 +02:00
fdc90caeee cli/command/container: deprecate DiffFormatWrite
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 13:52:04 +02:00
0db7b9f774 cli/command/container: newDiffContext: use struct-literal
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 13:50:31 +02:00
239b727834 cli/command/container: DiffFormatWrite: remove intermediate var
Also rename "ctx" argument; we shouldn't use this as name for things
that are not a context.Context.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 13:45:09 +02:00
907507e22a cli/command/container: deprecate NewDiffFormat
It's part of the presentation logic of the cli, and only used internally.
We can consider providing utilities for these, but better as part of
separate packages.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 13:45:05 +02:00
d8089e7d1b Merge pull request #6184 from Benehiko/deprecate-prompt-privilege-func
cli/command: deprecate RegistryAuthenticationPrivilegedFunc
2025-07-16 13:26:12 +02:00
29263e865b cli/command: remove usages of RegistryAuthenticationPrivilegedFunc
This patch deprecates the unused `RegistryAuthenticationPrivilegedFunc`.
The function would prompt the user when the registry returns a 403 after trying
the initial auth value set in `RegistryAuth`.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-07-16 12:49:19 +02:00
6bcc9ce730 Merge pull request #6174 from Benehiko/remove-prompt-privilege-func
cli/command: remove interactive login prompt from docker push/pull
2025-07-16 12:48:25 +02:00
46b8679315 Merge pull request #6155 from thaJeztah/bump_alpine
Dockerfile: update to alpine 3.22
2025-07-16 12:47:48 +02:00
980b856816 Merge pull request #6183 from thaJeztah/diff_simplify
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
cli/command/container: diff: remove redundant validation and cleanup
2025-07-16 12:32:48 +02:00
ea4c161067 Dockerfile: update to alpine 3.22
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 12:20:03 +02:00
9c256146ac Merge pull request #6181 from thaJeztah/fork_readCloserWrapper
remove uses of github.com/docker/docker/pkg/ioutils ReadCloserWrapper
2025-07-16 12:19:14 +02:00
bc01f8489d Merge pull request #6182 from thaJeztah/fork_longpath
remove use of github.com/docker/docker/pkg/longpath
2025-07-16 12:18:06 +02:00
ea2a0c3b8a Merge pull request #6177 from thaJeztah/rm_aliases
cli/command: remove some redundant import-aliases
2025-07-16 12:17:23 +02:00
3d985799d4 cli/command: remove some redundant import-aliases
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 12:12:41 +02:00
f5f3b027e8 Merge pull request #6178 from thaJeztah/bump_dev_tools
Dockerfile: update Buildx v0.25.0, compose v2.38.2
2025-07-16 12:07:46 +02:00
143f36133a Merge pull request #6179 from thaJeztah/bump_gotestsum
Dockerfile: bump gotest.tools/gotestsum v1.12.3 (for go1.25)
2025-07-16 12:07:17 +02:00
d7181e47e2 Merge pull request #6185 from thaJeztah/alpine_doc
Dockerfile: document ALPINE_VERSION build-arg
2025-07-16 12:06:17 +02:00
8b6436ecee Dockerfile: document ALPINE_VERSION build-arg
docker build --call outline .

    TARGET: binary

    BUILD ARG               VALUE    DESCRIPTION
    BASE_VARIANT            alpine
    ALPINE_VERSION          3.21     sets the version of the alpine base image to use, including for the golang image.
    GO_VERSION              1.24.5
    XX_VERSION              1.6.1
    GOVERSIONINFO_VERSION   v1.4.1
    GO_LINKMODE             static   defines if static or dynamic binary should be produced
    GO_BUILDTAGS                     defines additional build tags
    GO_STRIP                         strips debugging symbols if set
    CGO_ENABLED                      manually sets if cgo is used
    VERSION                          sets the version for the produced binary
    PACKAGER_NAME                    sets the company that produced the windows binary

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-16 09:51:14 +02:00
2b56b66b10 cli/command: remove interactive login prompt from docker push/pull
This patch removes the interactive prompts from `docker push/pull`.
The prompt would only execute on a response status code 403 from the registry
after trying the value set in `RegistryAuth`. Docker Hub could return 404
instead or 429, which would never execute the prompt.

The UX regarding the prompt is also questionable since the user might
not actually want to authenticate with a registry and the CLI could fail fast
instead. The user can always run `docker login` or set the `DOCKER_AUTH_CONFIG`
environment variable to get authenticated.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-07-16 08:43:35 +02:00
7d574b816d Merge pull request #6180 from thaJeztah/truncate_id
remove uses of github.com/docker/docker/pkg/stringid
2025-07-15 14:03:18 +02:00
0f2b709c7c cli/command/container: diff: remove redundant validation and cleanup
client.ContainerDiff already validates the given container name/ID, and
produces an error when empty, so we don't have to check for this;
abba330bbf/client/container_diff.go (L13-L16)

While updating, also;

- remove the diffOptions type, as there were no other options, and make
  the container name/ID a string argument.
- fix camelCase nameing of dockerCLI

Before this patch:

    docker diff ""
    Container name cannot be empty

With this patch:

    docker diff ""
    invalid container name or ID: value is empty

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-15 01:08:29 +02:00
7668b683d2 Merge pull request #6176 from thaJeztah/rm_use_AllowOverwriteDirWithFile
cli/command/container: don't set CopyToContainerOptions.AllowOverwriteDirWithFile
2025-07-14 23:17:34 +02:00
53d02ece89 remove use of github.com/docker/docker/pkg/longpath
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-14 22:34:36 +02:00
3600ebca76 remove uses of github.com/docker/docker/pkg/ioutils ReadCloserWrapper
It was the only utility we consumed from the package, and it's trivial
to implement, so let's create local copies of it.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-14 22:09:31 +02:00
9b047a501f remove uses of pkg/stringid.GenerateRandomID()
This utility was only used for testing, and to generate a random
suffix for Dockerfiles. As we don't need the same contract as
pkg/stringid.GenerateRandomID() (not allow all-numeric IDs as they
would not be usable for hostnames), we can use a local test-utility,
and local implementation for the random suffix instead.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-14 20:11:07 +02:00
e0f4bc699c cli/command/formatter: add TrunateID utility
We were depending on pkg/stringid to truncate IDs for presentation. While
traditionally, we used a fixed length for "truncated" IDs, this is not
a strict requirement (any ID-prefix should work, but conflicts may
happen on shorter IDs).

This patch adds a local `TruncateID()` utility in the formatter package;
it's currently using the same implementation and length as the
`stringid.TruncateID` function, but may diverge in future.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-14 20:10:56 +02:00
1264a59779 Dockerfile: bump gotest.tools/gotestsum v1.12.3 (for go1.25)
full diff: https://github.com/gotestyourself/gotestsum/compare/v1.12.0...v1.12.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-13 17:50:03 +02:00
e6b8cc1c7d Dockerfile: update buildx to v0.25.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-13 17:45:07 +02:00
50fa436c21 Dockerfile: update compose to v2.38.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-13 17:43:50 +02:00
0be687acc0 cli/command/container: don't set CopyToContainerOptions.AllowOverwriteDirWithFile
The `AllowOverwriteDirWithFile` option was added when reimplementing the
CLI using the API Client lib in [moby@1b2b91b]. Before that refactor, the
`noOverwriteDirNonDir` query argument [would be set unconditionally][1]
by the CLI, with no options to control the behavior.

It's unclear why the `noOverwriteDirNonDir` was implemented as opt-in (not
opt-out), as overwriting a file with a directory (or vice-versa) would
generally be unexpected behavior.

We're considering making `noOverwriteDirNonDir` unconditional on the daemon
side, and to deprecate the `AllowOverwriteDirWithFile` option. This patch
removes its use, as it was set to the default either way, and there's no
options to configure it from the CLI.

[1]: 8c9ad7b818/api/client/cp.go (L345-L346)
[moby@1b2b91b]: 1b2b91ba43

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-13 13:48:59 +02:00
c69d8bde4a Merge pull request #6173 from vvoland/fix-anchor-cdi
docs: fix CDI device configuration anchor
2025-07-11 15:27:46 +02:00
8eac03d5fa docs: fix CDI device configuration anchor
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-07-11 10:44:29 +02:00
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
0ba4362d69 Update markdown docs to indicate multi-platform support in image load/save.
Signed-off-by: Cesar Talledo <cesar.talledo@docker.com>
2025-06-10 16:42:39 -07:00
8993f54fc3 Add support for multiple platforms in docker image save
Signed-off-by: Cesar Talledo <cesar.talledo@docker.com>
2025-06-10 16:34:07 -07:00
38b99adc10 Add support for multiple platforms in docker image load.
Signed-off-by: Cesar Talledo <cesar.talledo@docker.com>
2025-06-10 16:34:02 -07: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
3b25977f82 Merge pull request #50110 from thaJeztah/remove_import_comments
all: remove // import comments
2025-05-30 20:35:54 +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
342f8bca25 builder: remove // import comments
These comments were added to enforce using the correct import path for
our packages ("github.com/docker/docker", not "github.com/moby/moby").
However, when working in go module mode (not GOPATH / vendor), they have
no effect, so their impact is limited.

Remove these imports in preparation of migrating our code to become an
actual go module.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-30 15:59:11 +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
7422403164 Merge pull request #49885 from mmorel-35/fix-staticcheck
fix staticcheck linting issues for golangci-lint v2
2025-05-01 17:08:40 +02:00
09a3c93f96 fix(QF1001): Apply De Morgan’s law
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-05-01 14:16:44 +02:00
a10a1e619b builder/remotecontext: remove unused named and "naked" returns
Also renamed some vars for clarity, renamed a error-returns to prevent
shadowing, and fixed some linter warnings about unhandled errors.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-04-26 15:47:11 +02:00
75f791d904 builder: use lazyregexp to compile regexes on first use
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-02 21:37:32 +01:00
8d3c0fb6dc tests: migrate to assert.ErrorContains when possible
If we have an error type that we're checking a substring against, we
should really be checking using ErrorContains to indicate the right
semantics to assert.

Mostly done using these transforms:

  find . -type f -name "*_test.go" | \
    xargs gofmt -w -r 'assert.Assert(t, is.ErrorContains(e, s)) -> assert.ErrorContains(t, e, s)'
  find . -type f -name "*_test.go" | \
    xargs gofmt -w -r 'assert.Assert(t, is.Contains(err.Error(), s)) -> assert.ErrorContains(t, err, s)'
  find . -type f -name "*_test.go" | \
    xargs gofmt -w -r 'assert.Check(t, is.Contains(err.Error(), s)) -> assert.Check(t, is.ErrorContains(err, s))'

As well as some small fixups to helpers that were doing
strings.Contains explicitly.

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2024-11-22 23:59:21 +11:00
45f09a1504 builder/remotecontext/git: remove redundant capturing of loop vars (copyloopvar)
builder/remotecontext/git/gitutils_test.go:116:3: The copy of the 'for' variable "tc" can be deleted (Go 1.22+) (copyloopvar)
            tc := tc
            ^

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-12 14:02:11 +01:00
ce639151e0 Merge pull request #47109 from whalelines/git-url-regex
Fix isGitURL regular expression
2024-01-18 14:02:57 -08:00
52c62bd13b Fix isGitURL regular expression
Escape period (.) so regular expression does not match any character before "git".

Signed-off-by: David Dooling <david.dooling@docker.com>
2024-01-18 14:14:08 -06:00
8f865184a6 builder/remotecontext: format code with gofumpt
Formatting the code with https://github.com/mvdan/gofumpt

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-29 00:25:21 +02:00
ee957e144b Merge pull request #44381 from thaJeztah/strings_cut
Replace uses of `strings.Split(N)` with `strings.Cut()`
2022-12-21 09:16:05 -08:00
6291744fa4 builder/remotecontext/git: use strings.Cut()
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-12-21 11:09:00 +01:00
60b326f814 builder/remotecontext/gitutils: switch back to os/exec
This is a partial revert of 389ada7188, which
switched from os/exec to the golang.org/x/sys/execabs package to mitigate
security issues (mainly on Windows) with lookups resolving to binaries in the
current directory.

from the go1.19 release notes https://go.dev/doc/go1.19#os-exec-path

> ## PATH lookups
>
> Command and LookPath no longer allow results from a PATH search to be found
> relative to the current directory. This removes a common source of security
> problems but may also break existing programs that depend on using, say,
> exec.Command("prog") to run a binary named prog (or, on Windows, prog.exe) in
> the current directory. See the os/exec package documentation for information
> about how best to update such programs.
>
> On Windows, Command and LookPath now respect the NoDefaultCurrentDirectoryInExePath
> environment variable, making it possible to disable the default implicit search
> of “.” in PATH lookups on Windows systems.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-11-09 12:28:17 +01:00
aa6ad06304 Merge pull request #44344 from thaJeztah/go1.18_compat
builder/remotecontext/git: allow building on go1.18
2022-10-21 19:38:54 +02:00
66713384c3 builder/remotecontext/git: allow building on go1.18
cmd.Environ() is new in go1.19, and not needed for this specific case.
Without this, trying to use this package in code that uses go1.18 will fail;

    builder/remotecontext/git/gitutils.go:216:23: cmd.Environ undefined (type *exec.Cmd has no field or method Environ)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-10-21 17:41:41 +02:00
5c21ec520e builder: add missing doc comment
Signed-off-by: Cory Snider <csnider@mirantis.com>
2022-10-20 16:47:18 -04:00
212213e81e builder: fix running git commands on Windows
Setting cmd.Env overrides the default of passing through the parent
process' environment, which works out fine most of the time, except when
it doesn't. For whatever reason, leaving out all the environment causes
git-for-windows sh.exe subprocesses to enter an infinite loop of
access violations during Cygwin initialization in certain environments
(specifically, our very own dev container image).

Signed-off-by: Cory Snider <csnider@mirantis.com>
2022-10-20 16:47:18 -04:00
bcd6c45731 builder: make git config isolation opt-in
While it is undesirable for the system or user git config to be used
when the daemon clones a Git repo, it could break workflows if it was
unconditionally applied to docker/cli as well.

Signed-off-by: Cory Snider <csnider@mirantis.com>
2022-10-20 16:47:18 -04:00
876fc1dac4 builder: isolate git from local system
Prevent git commands we run from reading the user or system
configuration, or cloning submodules from the local filesystem.

Signed-off-by: Cory Snider <csnider@mirantis.com>
2022-10-20 16:47:18 -04:00
3bfb30acd7 builder: explicitly set CWD for all git commands
Keep It Simple! Set the working directory for git commands by...setting
the git process's working directory. Git commands can be run in the
parent process's working directory by passing the empty string.

Signed-off-by: Cory Snider <csnider@mirantis.com>
2022-10-20 16:47:18 -04:00
3f4cc89f64 builder: modernize TestCheckoutGit
Make the test more debuggable by logging all git command output and
running each table-driven test case as a subtest.

Signed-off-by: Cory Snider <csnider@mirantis.com>
2022-10-20 16:47:18 -04:00
a12090d787 gofmt GoDoc comments with go1.19
Older versions of Go don't format comments, so committing this as
a separate commit, so that we can already make these changes before
we upgrade to Go 1.19.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-07-08 19:56:23 +02:00
f1b0ef127d Merge pull request #43477 from thaJeztah/deprecate_urlutil
pkg/urlutil: deprecate, and move to builder/remotecontext/urlutil
2022-04-13 17:16:15 +02:00
26a11366a7 builder/remotecontext/urlutil: simplify and improve documentation
Simplify some of the logic, and add documentation about the package,
as well as warnings that this package should not be used as a general-
purpose utility.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-04-12 19:58:09 +02:00
9e39630a05 pkg/urlutil: deprecate, and move to builder/remotecontext/urlutil
pkg/urlutil (despite its poorly chosen name) is not really intended as a generic
utility to handle URLs, and should only be used by the builder to handle (remote)
build contexts.

- IsURL() only does a very rudimentary check for http(s):// prefixes, without any
  other validation, but due to its name may give incorrect expectations.
- IsGitURL() is written specifically with docker build remote git contexts in
  mind, and has handling for backward-compatibility, where strings that are
  not URLs, but start with "github.com/" are accepted.

Because of the above, this patch:

- moves the package inside builder/remotecontext, close to where it's intended
  to be used (ideally this would be part of build/remotecontext itself, but this
  package imports many other dependencies, which would introduce those as extra
  dependencies in the CLI).
- deprecates pkg/urlutil, but adds aliases as there are some external consumers.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-04-12 19:58:05 +02:00
6d2a901118 refactor: move from io/ioutil to io and os package
The io/ioutil package has been deprecated in Go 1.16. This commit
replaces the existing io/ioutil functions with their new definitions in
io and os packages.

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2021-08-27 14:56:57 +08:00
389ada7188 Use golang.org/x/sys/execabs
Signed-off-by: Tibor Vass <tibor@docker.com>
2021-01-25 19:13:12 +00:00
613477b489 Merge pull request #41606 from thaJeztah/moby_sys_symlink
replace pkg/symlink with github.com/moby/sys/symlink
2020-11-03 09:58:08 -08:00
a4c8c72411 replace pkg/symlink with github.com/moby/sys/symlink
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-11-03 11:17:12 +01:00
5896d383ca bump gotest.tools v3.0.1 for compatibility with Go 1.14
full diff: https://github.com/gotestyourself/gotest.tools/compare/v2.3.0...v3.0.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-02-11 00:06:42 +01:00
ea850377cd builder/remotecontext: allow ssh:// urls for remote context
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-12-05 21:22:49 +01:00
2d0d4ce4af builder/remotecontext: use net/url instead of urlutil
urlutil.IsUrl() was merely checking if the url had a http(s)://
prefix, which is just as well handled through using url.Parse()

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-11-05 18:04:01 -08:00
a0d9b0cf0d TestParseRemoteURL: use subtests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-11-05 14:00:18 -08:00
b3d0327781 Merge pull request #39668 from thaJeztah/replace_gometalinter
Replace gometalinter with golangci-lint
2019-09-18 07:30:18 -07:00
70aef9f502 gosec: add ignore comments for reported issues that can be ignored
```
builder/remotecontext/remote.go:48:        G107: Potential HTTP request made with variable url (gosec)
builder/remotecontext/git/gitutils.go:145: G107: Potential HTTP request made with variable url (gosec)
builder/remotecontext/git/gitutils.go:147: G107: Potential HTTP request made with variable url (gosec)
pkg/fileutils/fileutils_test.go:185:       G303: File creation in shared tmp directory without using ioutil.Tempfile (gosec)
pkg/tarsum/tarsum_test.go:7:               G501: Blacklisted import `crypto/md5`: weak cryptographic primitive (gosec)
pkg/tarsum/tarsum_test.go:9:               G505: Blacklisted import `crypto/sha1`: weak cryptographic primitive (gosec)
```

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-09-18 12:57:43 +02:00
80dd489f21 Merge pull request #38944 from andrewhsu/gitutils
gitutils: add validation for ref
2019-03-27 02:03:47 +01:00
04e2a24a9e gitutils: add validation for ref
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit 723b107ca4fba14580a6cd971e63d8af2e7d2bbe)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2019-03-26 22:05:46 +00:00
84c375f430 Merge pull request #37243 from vdemeester/gotestyourself-with-tools
Update gotestyourself to gotest.tools
2018-06-13 16:23:26 +02:00
71672ece9c Update tests to use gotest.tools 👼
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-06-13 09:04:30 +02:00
db857b5d9c Post migration assertion fixes
Signed-off-by: Daniel Nephin <dnephin@docker.com>
2018-03-16 11:03:46 -04:00
242f176825 Automated migration using
gty-migrate-from-testify --ignore-build-tags

Signed-off-by: Daniel Nephin <dnephin@docker.com>
2018-03-16 11:03:43 -04:00
6ea4877cff Add canonical import comment
Signed-off-by: Daniel Nephin <dnephin@docker.com>
2018-02-05 16:51:57 -05:00
7bc503344a gitutils: remove checkout directory on error
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2017-12-08 11:58:13 -08:00
e2cc22d076 gitutils: fix checking out submodules
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2017-12-07 14:25:19 -08:00
e9831d75e2 Fix shallow git clone in docker-build
If the HEAD request fails, use a GET request to properly test if git
server is smart-http.

Signed-off-by: Andrew He <he.andrew.mail@gmail.com>
2017-07-25 13:20:59 -07:00
9450481b7e Move IsGitTransport() to gitutils
This function was only used inside gitutils,
and is written specifically for the requirements
there.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-06-26 10:07:04 -07:00
a6cc6cd878 Fix handling of remote "git@" notation
`docker build` accepts remote repositories
using either the `git://` notation, or `git@`.

Docker attempted to parse both as an URL, however,
`git@` is not an URL, but an argument to `git clone`.

Go 1.7 silently ignored this, and managed to
extract the needed information from these
remotes, however, Go 1.8 does a more strict
validation, and invalidated these.

This patch adds a different path for `git@` remotes,
to prevent them from being handled as URL (and
invalidated).

A test is also added, because there were no
tests for handling of `git@` remotes.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-06-26 10:02:12 -07:00
e907d54fe6 Move pkg/gitutils to remotecontext/git
Signed-off-by: Daniel Nephin <dnephin@docker.com>
2017-06-02 16:54:50 -04:00
1874 changed files with 70336 additions and 95188 deletions

View File

@ -35,7 +35,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
-
name: Create matrix
id: platforms
@ -88,7 +88,7 @@ jobs:
fi
-
name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: ${{ env.ARTIFACT_NAME }}
path: /tmp/out/*
@ -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
@ -142,7 +143,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
-
name: Create matrix
id: platforms

View File

@ -46,7 +46,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 2
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
@ -61,19 +61,19 @@ jobs:
ln -s vendor.sum go.sum
-
name: Update Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "1.24.3"
go-version: "1.25.4"
-
name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: go
-
name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v4
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: "/language:go"

View File

@ -37,14 +37,14 @@ jobs:
- alpine
- debian
engine-version:
- 29-rc # latest rc
- 28 # latest
- 27 # latest - 1
- 26 # github actions default
- 23 # mirantis lts
- 25 # mirantis lts
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
-
name: Update daemon.json
run: |

View File

@ -53,24 +53,25 @@ jobs:
fail-fast: false
matrix:
os:
- macos-13 # macOS 13 on Intel
- macos-14 # macOS 14 on arm64 (Apple Silicon M1)
- macos-14 # macOS 14 on arm64 (Apple Silicon M1)
- macos-15-intel # macOS 15 on Intel
- macos-15 # macOS 15 on arm64 (Apple Silicon M1)
# - windows-2022 # FIXME: some tests are failing on the Windows runner, as well as on Appveyor since June 24, 2018: https://ci.appveyor.com/project/docker/cli/history
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
path: ${{ env.GOPATH }}/src/github.com/docker/cli
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "1.24.3"
go-version: "1.25.4"
-
name: Test
run: |
go test -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/')
go test -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/|/cmd/docker-trust')
go tool cover -func=/tmp/coverage.txt
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
shell: bash

View File

@ -11,18 +11,23 @@ permissions:
on:
pull_request:
types: [opened, edited, labeled, unlabeled]
types: [opened, edited, labeled, unlabeled, synchronize]
jobs:
check-area-label:
check-labels:
runs-on: ubuntu-24.04
timeout-minutes: 120 # guardrails timeout for the whole job
steps:
- name: Missing `area/` label
if: contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'area/')
if: always() && contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'area/')
run: |
echo "::error::Every PR with an 'impact/*' label should also have an 'area/*' label"
exit 1
- name: Missing `kind/` label
if: always() && contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'kind/')
run: |
echo "::error::Every PR with an 'impact/*' label should also have a 'kind/*' label"
exit 1
- name: OK
run: exit 0

View File

@ -48,7 +48,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
-
name: Generate
shell: 'script --return --quiet --command "bash {0}"'
@ -74,7 +74,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
-
name: Run
shell: 'script --return --quiet --command "bash {0}"'

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.25.4"
timeout: 5m
@ -86,6 +86,8 @@ linters:
desc: Use github.com/moby/sys/userns instead.
- pkg: "github.com/containerd/containerd/platforms"
desc: The containerd platforms package was migrated to a separate module. Use github.com/containerd/platforms instead.
- pkg: "github.com/docker/docker/errdefs"
desc: Use github.com/containerd/errdefs instead.
- pkg: "github.com/docker/docker/pkg/system"
desc: This package should not be used unless strictly necessary.
- pkg: "github.com/docker/distribution/uuid"
@ -124,10 +126,9 @@ linters:
no-unaliased: true
alias:
# Enforce alias to prevent it accidentally being used instead of our
# own errdefs package (or vice-versa).
- pkg: github.com/containerd/errdefs
alias: cerrdefs
# Should no longer be aliased, because we no longer allow moby/docker errdefs.
- pkg: "github.com/docker/docker/errdefs"
alias: ""
- pkg: github.com/opencontainers/image-spec/specs-go/v1
alias: ocispec
# Enforce that gotest.tools/v3/assert/cmp is always aliased as "is"
@ -153,6 +154,7 @@ linters:
arguments: [200]
- name: unused-receiver # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-receiver
- name: use-any # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#use-any
- name: use-errors-new # https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md#use-errors-new
usetesting:
os-chdir: false # FIXME(thaJeztah): Disable `os.Chdir()` detections; should be automatically disabled on Go < 1.24; see https://github.com/docker/cli/pull/5835#issuecomment-2665302478
@ -221,6 +223,14 @@ linters:
linters:
- staticcheck
# Ignore deprecation linting for cli/command/stack/*.
#
# FIXME(thaJeztah): remove exception once these functions are un-exported or internal; see https://github.com/docker/cli/pull/6389
- text: '^(SA1019): '
path: "cli/command/stack"
linters:
- staticcheck
# Log a warning if an exclusion rule is unused.
# Default: false
warn-unused: true

View File

@ -64,11 +64,14 @@ Arko Dasgupta <arko@tetrate.io> <arko.dasgupta@docker.com>
Arko Dasgupta <arko@tetrate.io> <arkodg@users.noreply.github.com>
Arnaud Porterie <icecrime@gmail.com>
Arnaud Porterie <icecrime@gmail.com> <arnaud.porterie@docker.com>
Arthur Flageul <arthur.flageul@gmail.com>
Arthur Flageul <arthur.flageul@gmail.com> <arthur.flageul@docker.com>
Arthur Gautier <baloo@gandi.net> <superbaloo+registrations.github@superbaloo.net>
Arthur Peka <arthur.peka@outlook.com> <arthrp@users.noreply.github.com>
Austin Vazquez <austin.vazquez.dev@gmail.com>
Austin Vazquez <austin.vazquez.dev@gmail.com> <55906459+austinvazquez@users.noreply.github.com>
Austin Vazquez <austin.vazquez.dev@gmail.com> <macedonv@amazon.com>
Austin Vazquez <austin.vazquez@docker.com>
Austin Vazquez <austin.vazquez@docker.com> <55906459+austinvazquez@users.noreply.github.com>
Austin Vazquez <austin.vazquez@docker.com> <austin.vazquez.dev@gmail.com>
Austin Vazquez <austin.vazquez@docker.com> <macedonv@amazon.com>
Avi Miller <avi.miller@oracle.com> <avi.miller@gmail.com>
Ben Bonnefoy <frenchben@docker.com>
Ben Golub <ben.golub@dotcloud.com>
@ -150,6 +153,8 @@ Dave Henderson <dhenderson@gmail.com> <Dave.Henderson@ca.ibm.com>
Dave Tucker <dt@docker.com> <dave@dtucker.co.uk>
David Alvarez <david.alvarez@flyeralarm.com>
David Alvarez <david.alvarez@flyeralarm.com> <busilezas@gmail.com>
David Dooling <david.dooling@docker.com>
David Dooling <david.dooling@docker.com> <dooling@gmail.com>
David Karlsson <david.karlsson@docker.com>
David Karlsson <david.karlsson@docker.com> <35727626+dvdksn@users.noreply.github.com>
David M. Karr <davidmichaelkarr@gmail.com>

12
AUTHORS
View File

@ -63,6 +63,7 @@ Andreas Köhler <andi5.py@gmx.net>
Andres G. Aragoneses <knocte@gmail.com>
Andres Leon Rangel <aleon1220@gmail.com>
Andrew France <andrew@avito.co.uk>
Andrew He <he.andrew.mail@gmail.com>
Andrew Hsu <andrewhsu@docker.com>
Andrew Macpherson <hopscotch23@gmail.com>
Andrew McDonnell <bugs@andrewmcdonnell.net>
@ -86,11 +87,12 @@ Archimedes Trajano <developer@trajano.net>
Arko Dasgupta <arko@tetrate.io>
Arnaud Porterie <icecrime@gmail.com>
Arnaud Rebillout <elboulangero@gmail.com>
Arthur Flageul <arthur.flageul@gmail.com>
Arthur Peka <arthur.peka@outlook.com>
Ashly Mathew <ashly.mathew@sap.com>
Ashwini Oruganti <ashwini.oruganti@gmail.com>
Aslam Ahemad <aslamahemad@gmail.com>
Austin Vazquez <austin.vazquez.dev@gmail.com>
Austin Vazquez <austin.vazquez@docker.com>
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
Bardia Keyoumarsi <bkeyouma@ucsc.edu>
Barnaby Gray <barnaby@pickle.me.uk>
@ -135,10 +137,12 @@ Cao Weiwei <cao.weiwei30@zte.com.cn>
Carlo Mion <mion00@gmail.com>
Carlos Alexandro Becker <caarlos0@gmail.com>
Carlos de Paula <me@carlosedp.com>
carsontham <carsontham@outlook.com>
Carston Schilds <Carston.Schilds@visier.com>
Casey Korver <casey@korver.dev>
Ce Gao <ce.gao@outlook.com>
Cedric Davies <cedricda@microsoft.com>
Cesar Talledo <cesar.talledo@docker.com>
Cezar Sa Espinola <cezarsa@gmail.com>
Chad Faragher <wyckster@hotmail.com>
Chao Wang <wangchao.fnst@cn.fujitsu.com>
@ -220,7 +224,7 @@ David Alvarez <david.alvarez@flyeralarm.com>
David Beitey <david@davidjb.com>
David Calavera <david.calavera@gmail.com>
David Cramer <davcrame@cisco.com>
David Dooling <dooling@gmail.com>
David Dooling <david.dooling@docker.com>
David Gageot <david@gageot.net>
David Karlsson <david.karlsson@docker.com>
David le Blanc <systemmonkey42@users.noreply.github.com>
@ -265,6 +269,7 @@ Eli Uriegas <eli.uriegas@docker.com>
Eli Uriegas <seemethere101@gmail.com>
Elias Faxö <elias.faxo@tre.se>
Elliot Luo <956941328@qq.com>
Eng Zer Jun <engzerjun@gmail.com>
Eric Bode <eric.bode@foundries.io>
Eric Curtin <ericcurtin17@gmail.com>
Eric Engestrom <eric@engestrom.ch>
@ -345,6 +350,7 @@ Henning Sprang <henning.sprang@gmail.com>
Henry N <henrynmail-github@yahoo.de>
Hernan Garcia <hernandanielg@gmail.com>
Hongbin Lu <hongbin034@gmail.com>
Hossein Abbasi <16090309+hsnabszhdn@users.noreply.github.com>
Hu Keping <hukeping@huawei.com>
Huayi Zhang <irachex@gmail.com>
Hugo Chastel <Hugo-C@users.noreply.github.com>
@ -595,6 +601,7 @@ Michael Prokop <github@michael-prokop.at>
Michael Scharf <github@scharf.gr>
Michael Spetsiotis <michael_spets@hotmail.com>
Michael Steinert <mike.steinert@gmail.com>
Michael Tews <michael@tews.dev>
Michael West <mwest@mdsol.com>
Michal Minář <miminar@redhat.com>
Michał Czeraszkiewicz <czerasz@gmail.com>
@ -896,6 +903,7 @@ Wenlong Zhang <zhangwenlong@loongson.cn>
Wenzhi Liang <wenzhi.liang@gmail.com>
Wes Morgan <cap10morgan@gmail.com>
Wewang Xiaorenfine <wang.xiaoren@zte.com.cn>
Will Wang <willww64@gmail.com>
William Henry <whenry@redhat.com>
Xianglin Gao <xlgao@zju.edu.cn>
Xiaodong Liu <liuxiaodong@loongson.cn>

View File

@ -1,26 +1,43 @@
# syntax=docker/dockerfile:1
ARG BASE_VARIANT=alpine
ARG ALPINE_VERSION=3.21
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
# It must be a supported tag in the docker.io/library/alpine image repository
# that's also available as alpine image variant for the Golang version used.
ARG ALPINE_VERSION=3.22
ARG BASE_DEBIAN_DISTRO=bookworm
ARG GO_VERSION=1.24.3
ARG XX_VERSION=1.6.1
ARG GOVERSIONINFO_VERSION=v1.4.1
ARG GOTESTSUM_VERSION=v1.12.0
ARG GO_VERSION=1.25.4
# XX_VERSION specifies the version of the xx utility to use.
# It must be a valid tag in the docker.io/tonistiigi/xx image repository.
ARG XX_VERSION=1.7.0
# GOVERSIONINFO_VERSION is the version of GoVersionInfo to install.
# It must be a valid tag from https://github.com/josephspurrier/goversioninfo
ARG GOVERSIONINFO_VERSION=v1.5.0
# GOTESTSUM_VERSION sets the version of gotestsum to install in the dev container.
# It must be a valid tag in the https://github.com/gotestyourself/gotestsum repository.
ARG GOTESTSUM_VERSION=v1.13.0
# BUILDX_VERSION sets the version of buildx to use for the e2e tests.
# It must be a tag in the docker.io/docker/buildx-bin image repository
# on Docker Hub.
ARG BUILDX_VERSION=0.24.0
ARG COMPOSE_VERSION=v2.36.2
ARG BUILDX_VERSION=0.29.1
# COMPOSE_VERSION is the version of compose to install in the dev container.
# It must be a tag in the docker.io/docker/compose-bin image repository
# on Docker Hub.
ARG COMPOSE_VERSION=v2.40.0
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build-base-alpine
ENV GOTOOLCHAIN=local
COPY --link --from=xx / /
RUN apk add --no-cache bash clang lld llvm file git
RUN apk add --no-cache bash clang lld llvm file git git-daemon
WORKDIR /go/src/github.com/docker/cli
FROM build-base-alpine AS build-alpine
@ -80,7 +97,7 @@ ENV GO111MODULE=auto
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/go/pkg/mod \
gotestsum -- -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/')
gotestsum -- -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/|/cmd/docker-trust')
FROM scratch AS test-coverage
COPY --from=test /tmp/coverage.txt /coverage.txt
@ -105,10 +122,6 @@ FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx
FROM docker/compose-bin:${COMPOSE_VERSION} AS compose
FROM e2e-base-${BASE_VARIANT} AS e2e
ARG NOTARY_VERSION=v0.6.1
ADD --chmod=0755 https://github.com/theupdateframework/notary/releases/download/${NOTARY_VERSION}/notary-Linux-amd64 /usr/local/bin/notary
COPY --link e2e/testdata/notary/root-ca.cert /usr/share/ca-certificates/notary.cert
RUN echo 'notary.cert' >> /etc/ca-certificates.conf && update-ca-certificates
COPY --link --from=gotestsum /out/gotestsum /usr/bin/gotestsum
COPY --link --from=build /out ./build/
COPY --link --from=build-plugins /out ./build/

View File

@ -34,12 +34,12 @@ test: test-unit ## run tests
.PHONY: test-unit
test-unit: ## run unit tests, to change the output format use: GOTESTSUM_FORMAT=(dots|short|standard-quiet|short-verbose|standard-verbose) make test-unit
gotestsum -- $${TESTDIRS:-$(shell go list ./... | grep -vE '/vendor/|/e2e/')} $(TESTFLAGS)
gotestsum -- $${TESTDIRS:-$(shell go list ./... | grep -vE '/vendor/|/e2e/|/cmd/docker-trust')} $(TESTFLAGS)
.PHONY: test-coverage
test-coverage: ## run test coverage
mkdir -p $(CURDIR)/build/coverage
gotestsum -- $(shell go list ./... | grep -vE '/vendor/|/e2e/') -coverprofile=$(CURDIR)/build/coverage/coverage.txt
gotestsum -- $(shell go list ./... | grep -vE '/vendor/|/e2e/|/cmd/docker-trust') -coverprofile=$(CURDIR)/build/coverage/coverage.txt
.PHONY: lint
lint: ## run all the lint tools
@ -52,7 +52,7 @@ shellcheck: ## run shellcheck validation
.PHONY: fmt
fmt: ## run gofumpt (if present) or gofmt
@if command -v gofumpt > /dev/null; then \
gofumpt -w -d -lang=1.23 . ; \
gofumpt -w -d -lang=1.24 . ; \
else \
go list -f {{.Dir}} ./... | xargs gofmt -w -s -d ; \
fi
@ -69,6 +69,15 @@ dynbinary: ## build dynamically linked binary
plugins: ## build example CLI plugins
scripts/build/plugins
.PHONY: trust-plugin
trust-plugin: ## build docker-trust CLI plugins
scripts/build/trust-plugin
.PHONY: install-trust-plugin
install-trust-plugin: trust-plugin
install-trust-plugin: ## install docker-trust CLI plugins
install -D -m 0755 "$$(readlink -f build/docker-trust)" /usr/libexec/docker/cli-plugins/docker-trust
.PHONY: vendor
vendor: ## update vendor with go modules
rm -rf vendor

View File

@ -1 +1 @@
28.2.0-dev
29.0.0-dev

View File

@ -8,6 +8,7 @@ import (
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -25,7 +26,7 @@ func main() {
Short: "Print the API version of the server",
RunE: func(_ *cobra.Command, _ []string) error {
apiClient := dockerCLI.Client()
ping, err := apiClient.Ping(context.Background())
ping, err := apiClient.Ping(context.Background(), client.PingOptions{})
if err != nil {
return err
}

View File

@ -1,30 +0,0 @@
package manager
import "github.com/docker/cli/cli-plugins/metadata"
const (
// CommandAnnotationPlugin is added to every stub command added by
// AddPluginCommandStubs with the value "true" and so can be
// used to distinguish plugin stubs from regular commands.
CommandAnnotationPlugin = metadata.CommandAnnotationPlugin
// CommandAnnotationPluginVendor is added to every stub command
// added by AddPluginCommandStubs and contains the vendor of
// that plugin.
CommandAnnotationPluginVendor = metadata.CommandAnnotationPluginVendor
// CommandAnnotationPluginVersion is added to every stub command
// added by AddPluginCommandStubs and contains the version of
// that plugin.
CommandAnnotationPluginVersion = metadata.CommandAnnotationPluginVersion
// CommandAnnotationPluginInvalid is added to any stub command
// added by AddPluginCommandStubs for an invalid command (that
// is, one which failed it's candidate test) and contains the
// reason for the failure.
CommandAnnotationPluginInvalid = metadata.CommandAnnotationPluginInvalid
// CommandAnnotationPluginCommandPath is added to overwrite the
// command path for a plugin invocation.
CommandAnnotationPluginCommandPath = metadata.CommandAnnotationPluginCommandPath
)

View File

@ -6,12 +6,6 @@ import (
"github.com/docker/cli/cli-plugins/metadata"
)
// Candidate represents a possible plugin candidate, for mocking purposes
type Candidate interface {
Path() string
Metadata() ([]byte, error)
}
type candidate struct {
path string
}

View File

@ -32,14 +32,12 @@ func (c *fakeCandidate) Metadata() ([]byte, error) {
func TestValidateCandidate(t *testing.T) {
const (
goodPluginName = metadata.NamePrefix + "goodplugin"
builtinName = metadata.NamePrefix + "builtin"
builtinAlias = metadata.NamePrefix + "alias"
builtinName = metadata.NamePrefix + "builtin"
builtinAlias = metadata.NamePrefix + "alias"
badPrefixPath = "/usr/local/libexec/cli-plugins/wobble"
badNamePath = "/usr/local/libexec/cli-plugins/docker-123456"
goodPluginPath = "/usr/local/libexec/cli-plugins/" + goodPluginName
metaExperimental = `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`
badPrefixPath = "/usr/local/libexec/cli-plugins/wobble"
badNamePath = "/usr/local/libexec/cli-plugins/docker-123456"
goodPluginPath = "/usr/local/libexec/cli-plugins/" + goodPluginName
)
fakeroot := &cobra.Command{Use: "docker"}
@ -51,31 +49,103 @@ func TestValidateCandidate(t *testing.T) {
})
for _, tc := range []struct {
name string
c *fakeCandidate
name string
plugin *fakeCandidate
// Either err or invalid may be non-empty, but not both (both can be empty for a good plugin).
err string
invalid string
expVer string
}{
/* Each failing one of the tests */
{name: "empty path", c: &fakeCandidate{path: ""}, err: "plugin candidate path cannot be empty"},
{name: "bad prefix", c: &fakeCandidate{path: badPrefixPath}, err: fmt.Sprintf("does not have %q prefix", metadata.NamePrefix)},
{name: "bad path", c: &fakeCandidate{path: badNamePath}, invalid: "did not match"},
{name: "builtin command", c: &fakeCandidate{path: builtinName}, invalid: `plugin "builtin" duplicates builtin command`},
{name: "builtin alias", c: &fakeCandidate{path: builtinAlias}, invalid: `plugin "alias" duplicates an alias of builtin command "builtin"`},
{name: "fetch failure", c: &fakeCandidate{path: goodPluginPath, exec: false}, invalid: fmt.Sprintf("failed to fetch metadata: faked a failure to exec %q", goodPluginPath)},
{name: "metadata not json", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `xyzzy`}, invalid: "invalid character"},
{name: "empty schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`}, invalid: `plugin SchemaVersion "" is not valid`},
{name: "invalid schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`}, invalid: `plugin SchemaVersion "xyzzy" is not valid`},
{name: "no vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`}, invalid: "plugin metadata does not define a vendor"},
{name: "empty vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`}, invalid: "plugin metadata does not define a vendor"},
// This one should work
{name: "valid", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`}},
{name: "experimental + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental}},
// Invalid cases.
{
name: "empty path",
plugin: &fakeCandidate{path: ""},
err: "plugin candidate path cannot be empty",
},
{
name: "bad prefix",
plugin: &fakeCandidate{path: badPrefixPath},
err: fmt.Sprintf("does not have %q prefix", metadata.NamePrefix),
},
{
name: "bad path",
plugin: &fakeCandidate{path: badNamePath},
invalid: "did not match",
},
{
name: "builtin command",
plugin: &fakeCandidate{path: builtinName},
invalid: `plugin "builtin" duplicates builtin command`,
},
{
name: "builtin alias",
plugin: &fakeCandidate{path: builtinAlias},
invalid: `plugin "alias" duplicates an alias of builtin command "builtin"`,
},
{
name: "fetch failure",
plugin: &fakeCandidate{path: goodPluginPath, exec: false},
invalid: fmt.Sprintf("failed to fetch metadata: faked a failure to exec %q", goodPluginPath),
},
{
name: "metadata not json",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `xyzzy`},
invalid: "invalid character",
},
{
name: "empty schemaversion",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`},
invalid: `plugin SchemaVersion version cannot be empty`,
},
{
name: "invalid schemaversion",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`},
invalid: `plugin SchemaVersion "xyzzy" has wrong format: must be <major>.<minor>.<patch>`,
},
{
name: "invalid schemaversion major",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "2.0.0"}`},
invalid: `plugin SchemaVersion "2.0.0" is not supported: must be lower than 2.0.0`,
},
{
name: "no vendor",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`},
invalid: "plugin metadata does not define a vendor",
},
{
name: "empty vendor",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`},
invalid: "plugin metadata does not define a vendor",
},
// Valid cases.
{
name: "valid",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`},
expVer: "0.1.0",
},
{
// Including the deprecated "experimental" field should not break processing.
name: "with legacy experimental",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`},
expVer: "0.1.0",
},
{
// note that this may not be supported by older CLIs
name: "new minor schema version",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.2.0", "Vendor": "e2e-testing"}`},
expVer: "0.2.0",
},
{
// note that this may not be supported by older CLIs
name: "new major schema version",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "1.0.0", "Vendor": "e2e-testing"}`},
expVer: "1.0.0",
},
} {
t.Run(tc.name, func(t *testing.T) {
p, err := newPlugin(tc.c, fakeroot.Commands())
p, err := newPlugin(tc.plugin, fakeroot.Commands())
switch {
case tc.err != "":
assert.ErrorContains(t, err, tc.err)
@ -86,7 +156,7 @@ func TestValidateCandidate(t *testing.T) {
default:
assert.NilError(t, err)
assert.Equal(t, metadata.NamePrefix+p.Name, goodPluginName)
assert.Equal(t, p.SchemaVersion, "0.1.0")
assert.Equal(t, p.SchemaVersion, tc.expVer)
assert.Equal(t, p.Vendor, "e2e-testing")
}
})

View File

@ -38,6 +38,7 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
rootCmd.AddCommand(&cobra.Command{
Use: p.Name,
Short: p.ShortDescription,
Hidden: p.Hidden,
Run: func(_ *cobra.Command, _ []string) {},
Annotations: annotations,
DisableFlagParsing: true,

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package manager
@ -23,11 +23,6 @@ func (e *pluginError) Error() string {
return e.cause.Error()
}
// Cause satisfies the errors.causer interface for pluginError.
func (e *pluginError) Cause() error {
return e.cause
}
// Unwrap provides compatibility for Go 1.13 error chains.
func (e *pluginError) Unwrap() error {
return e.cause
@ -41,14 +36,11 @@ func (e *pluginError) MarshalText() (text []byte, err error) {
// wrapAsPluginError wraps an error in a pluginError with an
// additional message.
func wrapAsPluginError(err error, msg string) error {
if err == nil {
return nil
}
return &pluginError{cause: fmt.Errorf("%s: %w", msg, err)}
}
// NewPluginError creates a new pluginError, analogous to
// newPluginError creates a new pluginError, analogous to
// errors.Errorf.
func NewPluginError(msg string, args ...any) error {
func newPluginError(msg string, args ...any) error {
return &pluginError{cause: fmt.Errorf(msg, args...)}
}

View File

@ -10,7 +10,7 @@ import (
)
func TestPluginError(t *testing.T) {
err := NewPluginError("new error")
err := newPluginError("new error")
assert.Check(t, is.Error(err, "new error"))
inner := errors.New("testing")
@ -21,4 +21,7 @@ func TestPluginError(t *testing.T) {
actual, err := json.Marshal(err)
assert.Check(t, err)
assert.Check(t, is.Equal(`"wrapping: testing"`, string(actual)))
err = wrapAsPluginError(nil, "wrapping")
assert.Check(t, is.Error(err, "wrapping: %!w(<nil>)"))
}

View File

@ -2,6 +2,7 @@ package manager
import (
"context"
"errors"
"os"
"os/exec"
"path/filepath"
@ -9,28 +10,16 @@ import (
"strings"
"sync"
"github.com/containerd/errdefs"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/debug"
"github.com/fvbommel/sortorder"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)
const (
// ReexecEnvvar is the name of an ennvar which is set to the command
// used to originally invoke the docker CLI when executing a
// plugin. Assuming $PATH and $CWD remain unchanged this should allow
// the plugin to re-execute the original CLI.
ReexecEnvvar = metadata.ReexecEnvvar
// ResourceAttributesEnvvar is the name of the envvar that includes additional
// resource attributes for OTEL.
//
// Deprecated: The "OTEL_RESOURCE_ATTRIBUTES" env-var is part of the OpenTelemetry specification; users should define their own const for this. This const will be removed in the next release.
ResourceAttributesEnvvar = "OTEL_RESOURCE_ATTRIBUTES"
)
// errPluginNotFound is the error returned when a plugin could not be found.
type errPluginNotFound string
@ -40,17 +29,6 @@ func (e errPluginNotFound) Error() string {
return "Error: No such CLI plugin: " + string(e)
}
type notFound interface{ NotFound() }
// IsNotFound is true if the given error is due to a plugin not being found.
func IsNotFound(err error) bool {
if e, ok := err.(*pluginError); ok {
err = e.Cause()
}
_, ok := err.(notFound)
return ok
}
// getPluginDirs returns the platform-specific locations to search for plugins
// in order of preference.
//
@ -81,9 +59,17 @@ func addPluginCandidatesFromDir(res map[string][]string, d string) {
return
}
for _, dentry := range dentries {
switch dentry.Type() & os.ModeType { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list
case 0, os.ModeSymlink:
// Regular file or symlink, keep going
switch mode := dentry.Type() & os.ModeType; mode { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list
case os.ModeSymlink:
if !debug.IsEnabled() {
// Skip broken symlinks unless debug is enabled. With debug
// enabled, this will print a warning in "docker info".
if _, err := os.Stat(filepath.Join(d, dentry.Name())); errors.Is(err, os.ErrNotExist) {
continue
}
}
case 0:
// Regular file, keep going
default:
// Something else, ignore.
continue
@ -127,7 +113,7 @@ func getPlugin(name string, pluginDirs []string, rootcmd *cobra.Command) (*Plugi
if err != nil {
return nil, err
}
if !IsNotFound(p.Err) {
if !errdefs.IsNotFound(p.Err) {
p.ShadowedPaths = paths[1:]
}
return &p, nil
@ -164,7 +150,7 @@ func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, e
if err != nil {
return err
}
if !IsNotFound(p.Err) {
if !errdefs.IsNotFound(p.Err) {
p.ShadowedPaths = paths[1:]
mu.Lock()
defer mu.Unlock()
@ -185,15 +171,15 @@ func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, e
return plugins, nil
}
// PluginRunCommand returns an "os/exec".Cmd which when .Run() will execute the named plugin.
// PluginRunCommand returns an [os/exec.Cmd] which when [os/exec.Cmd.Run] will execute the named plugin.
// The rootcmd argument is referenced to determine the set of builtin commands in order to detect conficts.
// The error returned satisfies the IsNotFound() predicate if no plugin was found or if the first candidate plugin was invalid somehow.
// The error returned satisfies the [errdefs.IsNotFound] predicate if no plugin was found or if the first candidate plugin was invalid somehow.
func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Command) (*exec.Cmd, error) {
// This uses the full original args, not the args which may
// have been provided by cobra to our caller. This is because
// they lack e.g. global options which we must propagate here.
args := os.Args[1:]
if !pluginNameRe.MatchString(name) {
if !isValidPluginName(name) {
// We treat this as "not found" so that callers will
// fallback to their "invalid" command path.
return nil, errPluginNotFound(name)

View File

@ -5,6 +5,7 @@ import (
"strings"
"testing"
"github.com/containerd/errdefs"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/internal/test"
@ -37,7 +38,7 @@ func TestListPluginCandidates(t *testing.T) {
"plugins3-target", // Will be referenced as a symlink from below
fs.WithFile("docker-plugin1", ""),
fs.WithDir("ignored3"),
fs.WithSymlink("docker-brokensymlink", "broken"), // A broken symlink is still a candidate (but would fail tests later)
fs.WithSymlink("docker-brokensymlink", "broken"), // A broken symlink is ignored
fs.WithFile("non-plugin-symlinked", ""), // This shouldn't appear, but ...
fs.WithSymlink("docker-symlinked", "non-plugin-symlinked"), // ... this link to it should.
),
@ -71,9 +72,6 @@ func TestListPluginCandidates(t *testing.T) {
"hardlink2": {
dir.Join("plugins2", "docker-hardlink2"),
},
"brokensymlink": {
dir.Join("plugins3", "docker-brokensymlink"),
},
"symlinked": {
dir.Join("plugins3", "docker-symlinked"),
},
@ -131,7 +129,7 @@ echo '{"SchemaVersion":"0.1.0"}'`, fs.WithMode(0o777)),
_, err = GetPlugin("ccc", cli, &cobra.Command{})
assert.Error(t, err, "Error: No such CLI plugin: ccc")
assert.Assert(t, IsNotFound(err))
assert.Assert(t, errdefs.IsNotFound(err))
}
func TestListPluginsIsSorted(t *testing.T) {
@ -166,8 +164,8 @@ func TestErrPluginNotFound(t *testing.T) {
var err error = errPluginNotFound("test")
err.(errPluginNotFound).NotFound()
assert.Error(t, err, "Error: No such CLI plugin: test")
assert.Assert(t, IsNotFound(err))
assert.Assert(t, !IsNotFound(nil))
assert.Assert(t, errdefs.IsNotFound(err))
assert.Assert(t, !errdefs.IsNotFound(nil))
}
func TestGetPluginDirs(t *testing.T) {

View File

@ -1,23 +0,0 @@
package manager
import (
"github.com/docker/cli/cli-plugins/metadata"
)
const (
// NamePrefix is the prefix required on all plugin binary names
NamePrefix = metadata.NamePrefix
// MetadataSubcommandName is the name of the plugin subcommand
// which must be supported by every plugin and returns the
// plugin metadata.
MetadataSubcommandName = metadata.MetadataSubcommandName
// HookSubcommandName is the name of the plugin subcommand
// which must be implemented by plugins declaring support
// for hooks in their metadata.
HookSubcommandName = metadata.HookSubcommandName
)
// Metadata provided by the plugin.
type Metadata = metadata.Metadata

View File

@ -2,21 +2,20 @@ package manager
import (
"context"
"encoding"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/internal/lazyregexp"
"github.com/spf13/cobra"
)
var pluginNameRe = lazyregexp.New("^[a-z][a-z0-9]*$")
// Plugin represents a potential plugin with all it's metadata.
type Plugin struct {
metadata.Metadata
@ -31,12 +30,34 @@ type Plugin struct {
ShadowedPaths []string `json:",omitempty"`
}
// MarshalJSON implements [json.Marshaler] to handle marshaling the
// [Plugin.Err] field (Go doesn't marshal errors by default).
func (p *Plugin) MarshalJSON() ([]byte, error) {
type Alias Plugin // avoid recursion
cp := *p // shallow copy to avoid mutating original
if cp.Err != nil {
if _, ok := cp.Err.(encoding.TextMarshaler); !ok {
cp.Err = &pluginError{cp.Err}
}
}
return json.Marshal((*Alias)(&cp))
}
// pluginCandidate represents a possible plugin candidate, for mocking purposes.
type pluginCandidate interface {
Path() string
Metadata() ([]byte, error)
}
// newPlugin determines if the given candidate is valid and returns a
// Plugin. If the candidate fails one of the tests then `Plugin.Err`
// is set, and is always a `pluginError`, but the `Plugin` is still
// returned with no error. An error is only returned due to a
// non-recoverable error.
func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
path := c.Path()
if path == "" {
return Plugin{}, errors.New("plugin candidate path cannot be empty")
@ -62,8 +83,8 @@ func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
}
// Now apply the candidate tests, so these update p.Err.
if !pluginNameRe.MatchString(p.Name) {
p.Err = NewPluginError("plugin candidate %q did not match %q", p.Name, pluginNameRe.String())
if !isValidPluginName(p.Name) {
p.Err = newPluginError("plugin candidate %q did not match %q", p.Name, pluginNameFormat)
return p, nil
}
@ -75,11 +96,11 @@ func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
continue
}
if cmd.Name() == p.Name {
p.Err = NewPluginError("plugin %q duplicates builtin command", p.Name)
p.Err = newPluginError("plugin %q duplicates builtin command", p.Name)
return p, nil
}
if cmd.HasAlias(p.Name) {
p.Err = NewPluginError("plugin %q duplicates an alias of builtin command %q", p.Name, cmd.Name())
p.Err = newPluginError("plugin %q duplicates an alias of builtin command %q", p.Name, cmd.Name())
return p, nil
}
}
@ -95,17 +116,42 @@ func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
p.Err = wrapAsPluginError(err, "invalid metadata")
return p, nil
}
if p.Metadata.SchemaVersion != "0.1.0" {
p.Err = NewPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
if err := validateSchemaVersion(p.Metadata.SchemaVersion); err != nil {
p.Err = &pluginError{cause: err}
return p, nil
}
if p.Metadata.Vendor == "" {
p.Err = NewPluginError("plugin metadata does not define a vendor")
p.Err = newPluginError("plugin metadata does not define a vendor")
return p, nil
}
return p, nil
}
// validateSchemaVersion validates if the plugin's schemaVersion is supported.
//
// The current schema-version is "0.1.0", but we don't want to break compatibility
// until v2.0.0 of the schema version. Check for the major version to be < 2.0.0.
//
// Note that CLI versions before 28.4.1 may not support these versions as they were
// hard-coded to only accept "0.1.0".
func validateSchemaVersion(version string) error {
if version == "0.1.0" {
return nil
}
if version == "" {
return errors.New("plugin SchemaVersion version cannot be empty")
}
major, _, ok := strings.Cut(version, ".")
majorVersion, err := strconv.Atoi(major)
if !ok || err != nil {
return fmt.Errorf("plugin SchemaVersion %q has wrong format: must be <major>.<minor>.<patch>", version)
}
if majorVersion > 1 {
return fmt.Errorf("plugin SchemaVersion %q is not supported: must be lower than 2.0.0", version)
}
return nil
}
// RunHook executes the plugin's hooks command
// and returns its unprocessed output.
func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte, error) {
@ -124,3 +170,26 @@ func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte,
return hookCmdOutput, nil
}
// pluginNameFormat is used as part of errors for invalid plugin-names.
// We should consider making this less technical ("must start with "a-z",
// and only consist of lowercase alphanumeric characters").
const pluginNameFormat = `^[a-z][a-z0-9]*$`
func isValidPluginName(s string) bool {
if len(s) == 0 {
return false
}
// first character must be a-z
if c := s[0]; c < 'a' || c > 'z' {
return false
}
// followed by a-z or 0-9
for i := 1; i < len(s); i++ {
c := s[i]
if (c < 'a' || c > 'z') && (c < '0' || c > '9') {
return false
}
}
return true
}

View File

@ -0,0 +1,43 @@
package manager
import (
"encoding/json"
"errors"
"testing"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestPluginMarshal(t *testing.T) {
const jsonWithError = `{"Name":"some-plugin","Err":"something went wrong"}`
const jsonNoError = `{"Name":"some-plugin"}`
tests := []struct {
doc string
error error
expected string
}{
{
doc: "no error",
expected: jsonNoError,
},
{
doc: "regular error",
error: errors.New("something went wrong"),
expected: jsonWithError,
},
{
doc: "custom error",
error: newPluginError("something went wrong"),
expected: jsonWithError,
},
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
actual, err := json.Marshal(&Plugin{Name: "some-plugin", Err: tc.error})
assert.NilError(t, err)
assert.Check(t, is.Equal(string(actual), tc.expected))
})
}
}

View File

@ -33,4 +33,6 @@ type Metadata struct {
ShortDescription string `json:",omitempty"`
// URL is a pointer to the plugin's homepage.
URL string `json:",omitempty"`
// Hidden hides the plugin in completion and help message output.
Hidden bool `json:",omitempty"`
}

View File

@ -14,7 +14,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/connhelper"
"github.com/docker/cli/cli/debug"
"github.com/docker/docker/client"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel"
)
@ -80,19 +80,23 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadat
return cmd.Execute()
}
// Run is the top-level entry point to the CLI plugin framework. It should be called from your plugin's `main()` function.
func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) {
// Run is the top-level entry point to the CLI plugin framework. It should
// be called from the plugin's "main()" function. It initializes a new
// [command.DockerCli] instance with the given options before calling
// makeCmd to construct the plugin command, then invokes the plugin command
// using [RunPlugin].
func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata, ops ...command.CLIOption) {
otel.SetErrorHandler(debug.OTELErrorHandler)
dockerCli, err := command.NewDockerCli()
dockerCLI, err := command.NewDockerCli(ops...)
if err != nil {
fmt.Fprintln(os.Stderr, err)
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
plugin := makeCmd(dockerCli)
plugin := makeCmd(dockerCLI)
if err := RunPlugin(dockerCli, plugin, meta); err != nil {
if err := RunPlugin(dockerCLI, plugin, meta); err != nil {
var stErr cli.StatusError
if errors.As(err, &stErr) {
// StatusError should only be used for errors, and all errors should
@ -100,10 +104,10 @@ func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) {
if stErr.StatusCode == 0 { // FIXME(thaJeztah): this should never be used with a zero status-code. Check if we do this anywhere.
stErr.StatusCode = 1
}
_, _ = fmt.Fprintln(dockerCli.Err(), stErr)
_, _ = fmt.Fprintln(dockerCLI.Err(), stErr)
os.Exit(stErr.StatusCode)
}
_, _ = fmt.Fprintln(dockerCli.Err(), err)
_, _ = fmt.Fprintln(dockerCLI.Err(), err)
os.Exit(1)
}
}
@ -135,7 +139,7 @@ func withPluginClientConn(name string) command.CLIOption {
if err != nil {
return err
}
apiClient, err := client.NewClientWithOpts(client.WithDialContext(helper.Dialer))
apiClient, err := client.New(client.WithDialContext(helper.Dialer))
if err != nil {
return err
}
@ -164,6 +168,11 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta
DisableDescriptions: os.Getenv("DOCKER_CLI_DISABLE_COMPLETION_DESCRIPTION") != "",
},
}
// Disable file-completion by default. Most commands and flags should not
// complete with filenames.
cmd.CompletionOptions.SetDefaultShellCompDirective(cobra.ShellCompDirectiveNoFileComp)
opts, _ := cli.SetupPluginRootCommand(cmd)
cmd.SetIn(dockerCli.In())
@ -175,11 +184,24 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta
newMetadataSubcommand(plugin, meta),
)
cli.DisableFlagsInUseLine(cmd)
visitAll(cmd,
// prevent adding "[flags]" to the end of the usage line.
func(c *cobra.Command) { c.DisableFlagsInUseLine = true },
)
return cli.NewTopLevelCommand(cmd, dockerCli, opts, cmd.Flags())
}
// visitAll traverses all commands from the root.
func visitAll(root *cobra.Command, fns ...func(*cobra.Command)) {
for _, cmd := range root.Commands() {
visitAll(cmd, fns...)
}
for _, fn := range fns {
fn(root)
}
}
func newMetadataSubcommand(plugin *cobra.Command, meta metadata.Metadata) *cobra.Command {
if meta.ShortDescription == "" {
meta.ShortDescription = plugin.Short

View File

@ -0,0 +1,28 @@
package plugin
import (
"slices"
"testing"
"github.com/spf13/cobra"
)
func TestVisitAll(t *testing.T) {
root := &cobra.Command{Use: "root"}
sub1 := &cobra.Command{Use: "sub1"}
sub1sub1 := &cobra.Command{Use: "sub1sub1"}
sub1sub2 := &cobra.Command{Use: "sub1sub2"}
sub2 := &cobra.Command{Use: "sub2"}
root.AddCommand(sub1, sub2)
sub1.AddCommand(sub1sub1, sub1sub2)
var visited []string
visitAll(root, func(ccmd *cobra.Command) {
visited = append(visited, ccmd.Name())
})
expected := []string{"sub1sub1", "sub1sub2", "sub1", "sub2", "root"}
if !slices.Equal(expected, visited) {
t.Errorf("expected %#v, got %#v", expected, visited)
}
}

View File

@ -12,7 +12,6 @@ import (
"github.com/fvbommel/sortorder"
"github.com/moby/term"
"github.com/morikuni/aec"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@ -167,35 +166,6 @@ func (tcmd *TopLevelCommand) Initialize(ops ...command.CLIOption) error {
return tcmd.dockerCli.Initialize(tcmd.opts, ops...)
}
// VisitAll will traverse all commands from the root.
// This is different from the VisitAll of cobra.Command where only parents
// are checked.
func VisitAll(root *cobra.Command, fn func(*cobra.Command)) {
for _, cmd := range root.Commands() {
VisitAll(cmd, fn)
}
fn(root)
}
// DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all
// commands within the tree rooted at cmd.
func DisableFlagsInUseLine(cmd *cobra.Command) {
VisitAll(cmd, func(ccmd *cobra.Command) {
// do not add a `[flags]` to the end of the usage line.
ccmd.DisableFlagsInUseLine = true
})
}
// HasCompletionArg returns true if a cobra completion arg request is found.
func HasCompletionArg(args []string) bool {
for _, arg := range args {
if arg == cobra.ShellCompRequestCmd || arg == cobra.ShellCompNoDescRequestCmd {
return true
}
}
return false
}
var helpCommand = &cobra.Command{
Use: "help [command]",
Short: "Help about the command",
@ -204,7 +174,7 @@ var helpCommand = &cobra.Command{
RunE: func(c *cobra.Command, args []string) error {
cmd, args, e := c.Root().Find(args)
if cmd == nil || e != nil || len(args) > 0 {
return errors.Errorf("unknown help topic: %v", strings.Join(args, " "))
return fmt.Errorf("unknown help topic: %v", strings.Join(args, " "))
}
helpFunc := cmd.HelpFunc()
helpFunc(cmd, args)
@ -280,11 +250,12 @@ func commandAliases(cmd *cobra.Command) string {
if cmd.HasParent() {
parentPath = cmd.Parent().CommandPath() + " "
}
aliases := cmd.CommandPath()
var aliases strings.Builder
aliases.WriteString(cmd.CommandPath())
for _, alias := range cmd.Aliases {
aliases += ", " + parentPath + alias
aliases.WriteString(", " + parentPath + alias)
}
return aliases
return aliases.String()
}
func topCommands(cmd *cobra.Command) []*cobra.Command {
@ -380,13 +351,10 @@ func orchestratorSubCommands(cmd *cobra.Command) []*cobra.Command {
func allManagementSubCommands(cmd *cobra.Command) []*cobra.Command {
cmds := []*cobra.Command{}
for _, sub := range cmd.Commands() {
if isPlugin(sub) {
if invalidPluginReason(sub) == "" {
cmds = append(cmds, sub)
}
if invalidPluginReason(sub) != "" {
continue
}
if sub.IsAvailableCommand() && sub.HasSubCommands() {
if sub.IsAvailableCommand() && (isPlugin(sub) || sub.HasSubCommands()) {
cmds = append(cmds, sub)
}
}

View File

@ -10,28 +10,6 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func TestVisitAll(t *testing.T) {
root := &cobra.Command{Use: "root"}
sub1 := &cobra.Command{Use: "sub1"}
sub1sub1 := &cobra.Command{Use: "sub1sub1"}
sub1sub2 := &cobra.Command{Use: "sub1sub2"}
sub2 := &cobra.Command{Use: "sub2"}
root.AddCommand(sub1, sub2)
sub1.AddCommand(sub1sub1, sub1sub2)
// Take the opportunity to test DisableFlagsInUseLine too
DisableFlagsInUseLine(root)
var visited []string
VisitAll(root, func(ccmd *cobra.Command) {
visited = append(visited, ccmd.Name())
assert.Assert(t, ccmd.DisableFlagsInUseLine, "DisableFlagsInUseLine not set on %q", ccmd.Name())
})
expected := []string{"sub1sub1", "sub1sub2", "sub1", "sub2", "root"}
assert.DeepEqual(t, expected, visited)
}
func TestVendorAndVersion(t *testing.T) {
// Non plugin.
assert.Equal(t, vendorAndVersion(&cobra.Command{Use: "test"}), "")
@ -78,6 +56,33 @@ func TestInvalidPlugin(t *testing.T) {
assert.DeepEqual(t, invalidPlugins(root), []*cobra.Command{sub1}, cmpopts.IgnoreUnexported(cobra.Command{}))
}
func TestHiddenPlugin(t *testing.T) {
root := &cobra.Command{Use: "root"}
sub1 := &cobra.Command{
Use: "sub1",
Hidden: true,
Annotations: map[string]string{
metadata.CommandAnnotationPlugin: "true",
},
Run: func(cmd *cobra.Command, args []string) {},
}
sub1sub1 := &cobra.Command{Use: "sub1sub1"}
sub1sub2 := &cobra.Command{Use: "sub1sub2"}
sub2 := &cobra.Command{
Use: "sub2",
Annotations: map[string]string{
metadata.CommandAnnotationPlugin: "true",
},
Run: func(cmd *cobra.Command, args []string) {},
}
root.AddCommand(sub1, sub2)
sub1.AddCommand(sub1sub1, sub1sub2)
assert.DeepEqual(t, allManagementSubCommands(root), []*cobra.Command{sub2}, cmpopts.IgnoreFields(cobra.Command{}, "Run"), cmpopts.IgnoreUnexported(cobra.Command{}))
}
func TestCommandAliases(t *testing.T) {
root := &cobra.Command{Use: "root"}
sub := &cobra.Command{Use: "subcommand", Aliases: []string{"alias1", "alias2"}}

View File

@ -3,18 +3,17 @@ package builder
import (
"context"
"github.com/docker/docker/api/types/build"
"github.com/docker/docker/client"
"github.com/moby/moby/client"
)
type fakeClient struct {
client.Client
builderPruneFunc func(ctx context.Context, opts build.CachePruneOptions) (*build.CachePruneReport, error)
builderPruneFunc func(ctx context.Context, opts client.BuildCachePruneOptions) (client.BuildCachePruneResult, error)
}
func (c *fakeClient) BuildCachePrune(ctx context.Context, opts build.CachePruneOptions) (*build.CachePruneReport, error) {
func (c *fakeClient) BuildCachePrune(ctx context.Context, opts client.BuildCachePruneOptions) (client.BuildCachePruneResult, error) {
if c.builderPruneFunc != nil {
return c.builderPruneFunc(ctx, opts)
}
return nil, nil
return client.BuildCachePruneResult{}, nil
}

View File

@ -6,29 +6,41 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/internal/commands"
)
// NewBuilderCommand returns a cobra command for `builder` subcommands
func NewBuilderCommand(dockerCli command.Cli) *cobra.Command {
func init() {
commands.Register(newBuilderCommand)
commands.Register(func(c command.Cli) *cobra.Command {
return newBakeStubCommand(c)
})
}
// newBuilderCommand returns a cobra command for `builder` subcommands
func newBuilderCommand(dockerCLI command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "builder",
Short: "Manage builds",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
RunE: command.ShowHelp(dockerCLI.Err()),
Annotations: map[string]string{"version": "1.31"},
DisableFlagsInUseLine: true,
}
cmd.AddCommand(
NewPruneCommand(dockerCli),
image.NewBuildCommand(dockerCli),
newPruneCommand(dockerCLI),
// we should have a mechanism for registering sub-commands in the cli/internal/commands.Register function.
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
image.NewBuildCommand(dockerCLI),
)
return cmd
}
// NewBakeStubCommand returns a cobra command "stub" for the "bake" subcommand.
// newBakeStubCommand returns a cobra command "stub" for the "bake" subcommand.
// This command is a placeholder / stub that is dynamically replaced by an
// alias for "docker buildx bake" if BuildKit is enabled (and the buildx plugin
// installed).
func NewBakeStubCommand(dockerCLI command.Streams) *cobra.Command {
func newBakeStubCommand(dockerCLI command.Streams) *cobra.Command {
return &cobra.Command{
Use: "bake [OPTIONS] [TARGET...]",
Short: "Build from a file",
@ -40,5 +52,6 @@ func NewBakeStubCommand(dockerCLI command.Streams) *cobra.Command {
"aliases": "docker buildx bake",
"version": "1.31",
},
DisableFlagsInUseLine: true,
}
}

View File

@ -8,23 +8,30 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/system/pruner"
"github.com/docker/cli/internal/prompt"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/build"
"github.com/docker/go-units"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
type pruneOptions struct {
force bool
all bool
filter opts.FilterOpt
keepStorage opts.MemBytes
func init() {
// Register the prune command to run as part of "docker system prune"
if err := pruner.Register(pruner.TypeBuildCache, pruneFn); err != nil {
panic(err)
}
}
// NewPruneCommand returns a new cobra prune command for images
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
type pruneOptions struct {
force bool
all bool
filter opts.FilterOpt
reservedSpace opts.MemBytes
}
// newPruneCommand returns a new cobra prune command for images
func newPruneCommand(dockerCLI command.Cli) *cobra.Command {
options := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
@ -32,25 +39,26 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
Short: "Remove build cache",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCli, options)
spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCLI, options)
if err != nil {
return err
}
if output != "" {
fmt.Fprintln(dockerCli.Out(), output)
_, _ = fmt.Fprintln(dockerCLI.Out(), output)
}
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
_, _ = fmt.Fprintln(dockerCLI.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
return nil
},
Annotations: map[string]string{"version": "1.39"},
ValidArgsFunction: completion.NoComplete,
Annotations: map[string]string{"version": "1.39"},
ValidArgsFunction: cobra.NoFileCompletions,
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused build cache, not just dangling ones")
flags.Var(&options.filter, "filter", `Provide filter values (e.g. "until=24h")`)
flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")
flags.Var(&options.reservedSpace, "keep-storage", "Amount of disk space to keep for cache")
return cmd
}
@ -61,8 +69,7 @@ const (
)
func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := options.filter.Value()
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
warning := normalWarning
if options.all {
@ -78,15 +85,15 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
}
}
report, err := dockerCli.Client().BuildCachePrune(ctx, build.CachePruneOptions{
All: options.all,
KeepStorage: options.keepStorage.Value(), // FIXME(thaJeztah): rewrite to use new options; see https://github.com/moby/moby/pull/48720
Filters: pruneFilters,
resp, err := dockerCli.Client().BuildCachePrune(ctx, client.BuildCachePruneOptions{
All: options.all,
ReservedSpace: options.reservedSpace.Value(),
Filters: pruneFilters,
})
if err != nil {
return 0, "", err
}
report := resp.Report
if len(report.CachesDeleted) > 0 {
var sb strings.Builder
sb.WriteString("Deleted build cache objects:\n")
@ -104,7 +111,22 @@ type cancelledErr struct{ error }
func (cancelledErr) Cancelled() {}
// CachePrune executes a prune command for build cache
func CachePrune(ctx context.Context, dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
return runPrune(ctx, dockerCli, pruneOptions{force: true, all: all, filter: filter})
// pruneFn prunes the build cache for use in "docker system prune" and
// returns the amount of space reclaimed and a detailed output string.
func pruneFn(ctx context.Context, dockerCLI command.Cli, options pruner.PruneOptions) (uint64, string, error) {
if !options.Confirmed {
// Dry-run: perform validation and produce confirmation before pruning.
var confirmMsg string
if options.All {
confirmMsg = "all build cache"
} else {
confirmMsg = "unused build cache"
}
return 0, confirmMsg, cancelledErr{errors.New("builder prune has been cancelled")}
}
return runPrune(ctx, dockerCLI, pruneOptions{
force: true,
all: options.All,
filter: options.Filter,
})
}

View File

@ -7,7 +7,7 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/build"
"github.com/moby/moby/client"
)
func TestBuilderPromptTermination(t *testing.T) {
@ -15,11 +15,11 @@ func TestBuilderPromptTermination(t *testing.T) {
t.Cleanup(cancel)
cli := test.NewFakeCli(&fakeClient{
builderPruneFunc: func(ctx context.Context, opts build.CachePruneOptions) (*build.CachePruneReport, error) {
return nil, errors.New("fakeClient builderPruneFunc should not be called")
builderPruneFunc: func(ctx context.Context, opts client.BuildCachePruneOptions) (client.BuildCachePruneResult, error) {
return client.BuildCachePruneResult{}, errors.New("fakeClient builderPruneFunc should not be called")
},
})
cmd := NewPruneCommand(cli)
cmd := newPruneCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
test.TerminatePrompt(ctx, t, cmd, cli)

View File

@ -3,34 +3,33 @@ package checkpoint
import (
"context"
"github.com/docker/docker/api/types/checkpoint"
"github.com/docker/docker/client"
"github.com/moby/moby/client"
)
type fakeClient struct {
client.Client
checkpointCreateFunc func(container string, options checkpoint.CreateOptions) error
checkpointDeleteFunc func(container string, options checkpoint.DeleteOptions) error
checkpointListFunc func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error)
checkpointCreateFunc func(container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error)
checkpointDeleteFunc func(container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error)
checkpointListFunc func(container string, options client.CheckpointListOptions) (client.CheckpointListResult, error)
}
func (cli *fakeClient) CheckpointCreate(_ context.Context, container string, options checkpoint.CreateOptions) error {
func (cli *fakeClient) CheckpointCreate(_ context.Context, container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error) {
if cli.checkpointCreateFunc != nil {
return cli.checkpointCreateFunc(container, options)
}
return nil
return client.CheckpointCreateResult{}, nil
}
func (cli *fakeClient) CheckpointDelete(_ context.Context, container string, options checkpoint.DeleteOptions) error {
func (cli *fakeClient) CheckpointRemove(_ context.Context, container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error) {
if cli.checkpointDeleteFunc != nil {
return cli.checkpointDeleteFunc(container, options)
}
return nil
return client.CheckpointRemoveResult{}, nil
}
func (cli *fakeClient) CheckpointList(_ context.Context, container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) {
func (cli *fakeClient) CheckpointList(_ context.Context, container string, options client.CheckpointListOptions) (client.CheckpointListResult, error) {
if cli.checkpointListFunc != nil {
return cli.checkpointListFunc(container, options)
}
return []checkpoint.Summary{}, nil
return client.CheckpointListResult{}, nil
}

View File

@ -3,26 +3,32 @@ package checkpoint
import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/internal/commands"
"github.com/spf13/cobra"
)
// NewCheckpointCommand returns the `checkpoint` subcommand (only in experimental)
func NewCheckpointCommand(dockerCli command.Cli) *cobra.Command {
func init() {
commands.Register(newCheckpointCommand)
}
// newCheckpointCommand returns the `checkpoint` subcommand (only in experimental)
func newCheckpointCommand(dockerCLI command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "checkpoint",
Short: "Manage checkpoints",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
RunE: command.ShowHelp(dockerCLI.Err()),
Annotations: map[string]string{
"experimental": "",
"ostype": "linux",
"version": "1.25",
},
DisableFlagsInUseLine: true,
}
cmd.AddCommand(
newCreateCommand(dockerCli),
newListCommand(dockerCli),
newRemoveCommand(dockerCli),
newCreateCommand(dockerCLI),
newListCommand(dockerCLI),
newRemoveCommand(dockerCLI),
)
return cmd
}

View File

@ -6,8 +6,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/checkpoint"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -18,7 +17,7 @@ type createOptions struct {
leaveRunning bool
}
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
var opts createOptions
cmd := &cobra.Command{
@ -28,9 +27,10 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
opts.container = args[0]
opts.checkpoint = args[1]
return runCreate(cmd.Context(), dockerCli, opts)
return runCreate(cmd.Context(), dockerCLI, opts)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -41,7 +41,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
}
func runCreate(ctx context.Context, dockerCLI command.Cli, opts createOptions) error {
err := dockerCLI.Client().CheckpointCreate(ctx, opts.container, checkpoint.CreateOptions{
_, err := dockerCLI.Client().CheckpointCreate(ctx, opts.container, client.CheckpointCreateOptions{
CheckpointID: opts.checkpoint,
CheckpointDir: opts.checkpointDir,
Exit: !opts.leaveRunning,

View File

@ -8,7 +8,7 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/checkpoint"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@ -16,7 +16,7 @@ import (
func TestCheckpointCreateErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointCreateFunc func(container string, options checkpoint.CreateOptions) error
checkpointCreateFunc func(container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error)
expectedError string
}{
{
@ -29,8 +29,8 @@ func TestCheckpointCreateErrors(t *testing.T) {
},
{
args: []string{"foo", "bar"},
checkpointCreateFunc: func(container string, options checkpoint.CreateOptions) error {
return errors.New("error creating checkpoint for container foo")
checkpointCreateFunc: func(container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error) {
return client.CheckpointCreateResult{}, errors.New("error creating checkpoint for container foo")
},
expectedError: "error creating checkpoint for container foo",
},
@ -59,12 +59,12 @@ func TestCheckpointCreateWithOptions(t *testing.T) {
leaveRunning := strconv.FormatBool(tc)
t.Run("leave-running="+leaveRunning, func(t *testing.T) {
var actualContainerName string
var actualOptions checkpoint.CreateOptions
var actualOptions client.CheckpointCreateOptions
cli := test.NewFakeCli(&fakeClient{
checkpointCreateFunc: func(container string, options checkpoint.CreateOptions) error {
checkpointCreateFunc: func(container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error) {
actualContainerName = container
actualOptions = options
return nil
return client.CheckpointCreateResult{}, nil
},
})
cmd := newCreateCommand(cli)
@ -75,7 +75,7 @@ func TestCheckpointCreateWithOptions(t *testing.T) {
assert.Check(t, cmd.Flags().Set("checkpoint-dir", checkpointDir))
assert.NilError(t, cmd.Execute())
assert.Check(t, is.Equal(actualContainerName, containerName))
expected := checkpoint.CreateOptions{
expected := client.CheckpointCreateOptions{
CheckpointID: checkpointName,
CheckpointDir: checkpointDir,
Exit: !tc,

View File

@ -2,7 +2,7 @@ package checkpoint
import (
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/checkpoint"
"github.com/moby/moby/api/types/checkpoint"
)
const (
@ -10,25 +10,31 @@ const (
checkpointNameHeader = "CHECKPOINT NAME"
)
// NewFormat returns a format for use with a checkpoint Context
func NewFormat(source string) formatter.Format {
// newFormat returns a format for use with a checkpointContext.
func newFormat(source string) formatter.Format {
if source == formatter.TableFormatKey {
return defaultCheckpointFormat
}
return formatter.Format(source)
}
// FormatWrite writes formatted checkpoints using the Context
func FormatWrite(ctx formatter.Context, checkpoints []checkpoint.Summary) error {
render := func(format func(subContext formatter.SubContext) error) error {
// formatWrite writes formatted checkpoints using the Context
func formatWrite(fmtCtx formatter.Context, checkpoints []checkpoint.Summary) error {
cpContext := &checkpointContext{
HeaderContext: formatter.HeaderContext{
Header: formatter.SubHeaderContext{
"Name": checkpointNameHeader,
},
},
}
return fmtCtx.Write(cpContext, func(format func(subContext formatter.SubContext) error) error {
for _, cp := range checkpoints {
if err := format(&checkpointContext{c: cp}); err != nil {
return err
}
}
return nil
}
return ctx.Write(newCheckpointContext(), render)
})
}
type checkpointContext struct {
@ -36,14 +42,6 @@ type checkpointContext struct {
c checkpoint.Summary
}
func newCheckpointContext() *checkpointContext {
cpCtx := checkpointContext{}
cpCtx.Header = formatter.SubHeaderContext{
"Name": checkpointNameHeader,
}
return &cpCtx
}
func (c *checkpointContext) MarshalJSON() ([]byte, error) {
return formatter.MarshalJSON(c)
}

View File

@ -5,7 +5,7 @@ import (
"testing"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/checkpoint"
"github.com/moby/moby/api/types/checkpoint"
"gotest.tools/v3/assert"
)
@ -15,7 +15,7 @@ func TestCheckpointContextFormatWrite(t *testing.T) {
expected string
}{
{
formatter.Context{Format: NewFormat(defaultCheckpointFormat)},
formatter.Context{Format: newFormat(defaultCheckpointFormat)},
`CHECKPOINT NAME
checkpoint-1
checkpoint-2
@ -23,14 +23,14 @@ checkpoint-3
`,
},
{
formatter.Context{Format: NewFormat("{{.Name}}")},
formatter.Context{Format: newFormat("{{.Name}}")},
`checkpoint-1
checkpoint-2
checkpoint-3
`,
},
{
formatter.Context{Format: NewFormat("{{.Name}}:")},
formatter.Context{Format: newFormat("{{.Name}}:")},
`checkpoint-1:
checkpoint-2:
checkpoint-3:
@ -41,7 +41,7 @@ checkpoint-3:
for _, testcase := range cases {
out := bytes.NewBufferString("")
testcase.context.Output = out
err := FormatWrite(testcase.context, []checkpoint.Summary{
err := formatWrite(testcase.context, []checkpoint.Summary{
{Name: "checkpoint-1"},
{Name: "checkpoint-2"},
{Name: "checkpoint-3"},

View File

@ -7,7 +7,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/checkpoint"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -15,7 +15,7 @@ type listOptions struct {
checkpointDir string
}
func newListCommand(dockerCli command.Cli) *cobra.Command {
func newListCommand(dockerCLI command.Cli) *cobra.Command {
var opts listOptions
cmd := &cobra.Command{
@ -24,9 +24,10 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
Short: "List checkpoints for a container",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runList(cmd.Context(), dockerCli, args[0], opts)
return runList(cmd.Context(), dockerCLI, args[0], opts)
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -35,8 +36,8 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runList(ctx context.Context, dockerCli command.Cli, container string, opts listOptions) error {
checkpoints, err := dockerCli.Client().CheckpointList(ctx, container, checkpoint.ListOptions{
func runList(ctx context.Context, dockerCLI command.Cli, container string, opts listOptions) error {
checkpoints, err := dockerCLI.Client().CheckpointList(ctx, container, client.CheckpointListOptions{
CheckpointDir: opts.checkpointDir,
})
if err != nil {
@ -44,8 +45,8 @@ func runList(ctx context.Context, dockerCli command.Cli, container string, opts
}
cpCtx := formatter.Context{
Output: dockerCli.Out(),
Format: NewFormat(formatter.TableFormatKey),
Output: dockerCLI.Out(),
Format: newFormat(formatter.TableFormatKey),
}
return FormatWrite(cpCtx, checkpoints)
return formatWrite(cpCtx, checkpoints.Items)
}

View File

@ -6,7 +6,8 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/checkpoint"
"github.com/moby/moby/api/types/checkpoint"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
@ -15,7 +16,7 @@ import (
func TestCheckpointListErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointListFunc func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error)
checkpointListFunc func(container string, options client.CheckpointListOptions) (client.CheckpointListResult, error)
expectedError string
}{
{
@ -28,8 +29,8 @@ func TestCheckpointListErrors(t *testing.T) {
},
{
args: []string{"foo"},
checkpointListFunc: func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) {
return []checkpoint.Summary{}, errors.New("error getting checkpoints for container foo")
checkpointListFunc: func(container string, options client.CheckpointListOptions) (client.CheckpointListResult, error) {
return client.CheckpointListResult{}, errors.New("error getting checkpoints for container foo")
},
expectedError: "error getting checkpoints for container foo",
},
@ -50,17 +51,19 @@ func TestCheckpointListErrors(t *testing.T) {
func TestCheckpointListWithOptions(t *testing.T) {
var containerID, checkpointDir string
cli := test.NewFakeCli(&fakeClient{
checkpointListFunc: func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) {
checkpointListFunc: func(container string, options client.CheckpointListOptions) (client.CheckpointListResult, error) {
containerID = container
checkpointDir = options.CheckpointDir
return []checkpoint.Summary{
{Name: "checkpoint-foo"},
return client.CheckpointListResult{
Items: []checkpoint.Summary{
{Name: "checkpoint-foo"},
},
}, nil
},
})
cmd := newListCommand(cli)
cmd.SetArgs([]string{"container-foo"})
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.Check(t, cmd.Flags().Set("checkpoint-dir", "/dir/foo"))
assert.NilError(t, cmd.Execute())
assert.Check(t, is.Equal("container-foo", containerID))
assert.Check(t, is.Equal("/dir/foo", checkpointDir))

View File

@ -1,11 +1,9 @@
package checkpoint
import (
"context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types/checkpoint"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -13,7 +11,7 @@ type removeOptions struct {
checkpointDir string
}
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
func newRemoveCommand(dockerCLI command.Cli) *cobra.Command {
var opts removeOptions
cmd := &cobra.Command{
@ -22,8 +20,14 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
Short: "Remove a checkpoint",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return runRemove(cmd.Context(), dockerCli, args[0], args[1], opts)
containerID, checkpointID := args[0], args[1]
_, err := dockerCLI.Client().CheckpointRemove(cmd.Context(), containerID, client.CheckpointRemoveOptions{
CheckpointID: checkpointID,
CheckpointDir: opts.checkpointDir,
})
return err
},
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -31,10 +35,3 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runRemove(ctx context.Context, dockerCli command.Cli, container string, checkpointID string, opts removeOptions) error {
return dockerCli.Client().CheckpointDelete(ctx, container, checkpoint.DeleteOptions{
CheckpointID: checkpointID,
CheckpointDir: opts.checkpointDir,
})
}

View File

@ -6,7 +6,7 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/checkpoint"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@ -14,7 +14,7 @@ import (
func TestCheckpointRemoveErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointDeleteFunc func(container string, options checkpoint.DeleteOptions) error
checkpointDeleteFunc func(container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error)
expectedError string
}{
{
@ -27,8 +27,8 @@ func TestCheckpointRemoveErrors(t *testing.T) {
},
{
args: []string{"foo", "bar"},
checkpointDeleteFunc: func(container string, options checkpoint.DeleteOptions) error {
return errors.New("error deleting checkpoint")
checkpointDeleteFunc: func(container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error) {
return client.CheckpointRemoveResult{}, errors.New("error deleting checkpoint")
},
expectedError: "error deleting checkpoint",
},
@ -49,16 +49,16 @@ func TestCheckpointRemoveErrors(t *testing.T) {
func TestCheckpointRemoveWithOptions(t *testing.T) {
var containerID, checkpointID, checkpointDir string
cli := test.NewFakeCli(&fakeClient{
checkpointDeleteFunc: func(container string, options checkpoint.DeleteOptions) error {
checkpointDeleteFunc: func(container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error) {
containerID = container
checkpointID = options.CheckpointID
checkpointDir = options.CheckpointDir
return nil
return client.CheckpointRemoveResult{}, nil
},
})
cmd := newRemoveCommand(cli)
cmd.SetArgs([]string{"container-foo", "checkpoint-bar"})
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.Check(t, cmd.Flags().Set("checkpoint-dir", "/dir/foo"))
assert.NilError(t, cmd.Execute())
assert.Check(t, is.Equal("container-foo", containerID))
assert.Check(t, is.Equal("checkpoint-bar", checkpointID))

View File

@ -1,10 +1,11 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package command
import (
"context"
"errors"
"fmt"
"io"
"os"
@ -23,11 +24,8 @@ import (
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/cli/version"
dopts "github.com/docker/cli/opts"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types/build"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -45,12 +43,9 @@ type Cli interface {
Client() client.APIClient
Streams
SetIn(in *streams.In)
Apply(ops ...CLIOption) error
config.Provider
ServerInfo() ServerInfo
DefaultVersion() string
CurrentVersion() string
ContentTrustEnabled() bool
BuildKitEnabled() (bool, error)
ContextStore() store.Store
CurrentContext() string
@ -70,7 +65,6 @@ type DockerCli struct {
err *streams.Out
client client.APIClient
serverInfo ServerInfo
contentTrust bool
contextStore store.Store
currentContext string
init sync.Once
@ -78,6 +72,7 @@ type DockerCli struct {
dockerEndpoint docker.Endpoint
contextStoreConfig *store.Config
initTimeout time.Duration
userAgent string
res telemetryResource
// baseCtx is the base context used for internal operations. In the future
@ -88,17 +83,12 @@ type DockerCli struct {
enableGlobalMeter, enableGlobalTracer bool
}
// DefaultVersion returns [api.DefaultVersion].
func (*DockerCli) DefaultVersion() string {
return api.DefaultVersion
}
// CurrentVersion returns the API version currently negotiated, or the default
// version otherwise.
func (cli *DockerCli) CurrentVersion() string {
_ = cli.initialize()
if cli.client == nil {
return api.DefaultVersion
return client.MaxAPIVersion
}
return cli.client.ClientVersion()
}
@ -157,19 +147,13 @@ func (cli *DockerCli) ServerInfo() ServerInfo {
return cli.serverInfo
}
// ContentTrustEnabled returns whether content trust has been enabled by an
// environment variable.
func (cli *DockerCli) ContentTrustEnabled() bool {
return cli.contentTrust
}
// BuildKitEnabled returns buildkit is enabled or not.
func (cli *DockerCli) BuildKitEnabled() (bool, error) {
// use DOCKER_BUILDKIT env var value if set and not empty
if v := os.Getenv("DOCKER_BUILDKIT"); v != "" {
enabled, err := strconv.ParseBool(v)
if err != nil {
return false, errors.Wrap(err, "DOCKER_BUILDKIT environment variable expects boolean value")
return false, fmt.Errorf("DOCKER_BUILDKIT environment variable expects boolean value: %w", err)
}
return enabled, nil
}
@ -269,7 +253,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
cli.contextStore = &ContextStoreWithDefault{
Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig),
Resolver: func() (*DefaultContext, error) {
return ResolveDefaultContext(cli.options, *cli.contextStoreConfig)
return resolveDefaultContext(cli.options, *cli.contextStoreConfig)
},
}
@ -282,6 +266,17 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
}
filterResourceAttributesEnvvar()
// early return if GODEBUG is already set or the docker context is
// the default context, i.e. is a virtual context where we won't override
// any GODEBUG values.
if v := os.Getenv("GODEBUG"); cli.currentContext == DefaultContextName || v != "" {
return nil
}
meta, err := cli.contextStore.GetMetadata(cli.currentContext)
if err == nil {
setGoDebug(meta)
}
return nil
}
@ -295,17 +290,17 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.
contextStore := &ContextStoreWithDefault{
Store: store.New(config.ContextStoreDir(), storeConfig),
Resolver: func() (*DefaultContext, error) {
return ResolveDefaultContext(opts, storeConfig)
return resolveDefaultContext(opts, storeConfig)
},
}
endpoint, err := resolveDockerEndpoint(contextStore, resolveContextName(opts, configFile))
if err != nil {
return nil, errors.Wrap(err, "unable to resolve docker endpoint")
return nil, fmt.Errorf("unable to resolve docker endpoint: %w", err)
}
return newAPIClientFromEndpoint(endpoint, configFile)
return newAPIClientFromEndpoint(endpoint, configFile, client.WithUserAgent(UserAgent()))
}
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile) (client.APIClient, error) {
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile, extraOpts ...client.Opt) (client.APIClient, error) {
opts, err := ep.ClientOpts()
if err != nil {
return nil, err
@ -313,8 +308,15 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF
if len(configFile.HTTPHeaders) > 0 {
opts = append(opts, client.WithHTTPHeaders(configFile.HTTPHeaders))
}
opts = append(opts, withCustomHeadersFromEnv(), client.WithUserAgent(UserAgent()))
return client.NewClientWithOpts(opts...)
withCustomHeaders, err := withCustomHeadersFromEnv()
if err != nil {
return nil, err
}
if withCustomHeaders != nil {
opts = append(opts, withCustomHeaders)
}
opts = append(opts, extraOpts...)
return client.New(opts...)
}
func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint, error) {
@ -375,24 +377,21 @@ func (cli *DockerCli) initializeFromClient() {
ctx, cancel := context.WithTimeout(cli.baseCtx, cli.getInitTimeout())
defer cancel()
ping, err := cli.client.Ping(ctx)
ping, err := cli.client.Ping(ctx, client.PingOptions{
NegotiateAPIVersion: true,
ForceNegotiate: true,
})
if err != nil {
// Default to true if we fail to connect to daemon
cli.serverInfo = ServerInfo{HasExperimental: true}
if ping.APIVersion != "" {
cli.client.NegotiateAPIVersionPing(ping)
}
return
}
cli.serverInfo = ServerInfo{
HasExperimental: ping.Experimental,
OSType: ping.OSType,
BuildkitVersion: ping.BuilderVersion,
SwarmStatus: ping.SwarmStatus,
}
cli.client.NegotiateAPIVersionPing(ping)
}
// ContextStore returns the ContextStore
@ -475,15 +474,67 @@ func (cli *DockerCli) getDockerEndPoint() (ep docker.Endpoint, err error) {
return resolveDockerEndpoint(cli.contextStore, cn)
}
// setGoDebug is an escape hatch that sets the GODEBUG environment
// variable value using docker context metadata.
//
// {
// "Name": "my-context",
// "Metadata": { "GODEBUG": "x509negativeserial=1" }
// }
//
// WARNING: Setting x509negativeserial=1 allows Go's x509 library to accept
// X.509 certificates with negative serial numbers.
// This behavior is deprecated and non-compliant with current security
// standards (RFC 5280). Accepting negative serial numbers can introduce
// serious security vulnerabilities, including the risk of certificate
// collision or bypass attacks.
// This option should only be used for legacy compatibility and never in
// production environments.
// Use at your own risk.
func setGoDebug(meta store.Metadata) {
fieldName := "GODEBUG"
godebugEnv := os.Getenv(fieldName)
// early return if GODEBUG is already set. We don't want to override what
// the user already sets.
if godebugEnv != "" {
return
}
var cfg any
var ok bool
switch m := meta.Metadata.(type) {
case DockerContext:
cfg, ok = m.AdditionalFields[fieldName]
if !ok {
return
}
case map[string]any:
cfg, ok = m[fieldName]
if !ok {
return
}
default:
return
}
v, ok := cfg.(string)
if !ok {
return
}
// set the GODEBUG environment variable with whatever was in the context
_ = os.Setenv(fieldName, v)
}
func (cli *DockerCli) initialize() error {
cli.init.Do(func() {
cli.dockerEndpoint, cli.initErr = cli.getDockerEndPoint()
if cli.initErr != nil {
cli.initErr = errors.Wrap(cli.initErr, "unable to resolve docker endpoint")
cli.initErr = fmt.Errorf("unable to resolve docker endpoint: %w", cli.initErr)
return
}
if cli.client == nil {
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile); cli.initErr != nil {
ops := []client.Opt{client.WithUserAgent(cli.userAgent)}
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil {
return
}
}
@ -495,16 +546,6 @@ func (cli *DockerCli) initialize() error {
return cli.initErr
}
// Apply all the operation on the cli
func (cli *DockerCli) Apply(ops ...CLIOption) error {
for _, op := range ops {
if err := op(cli); err != nil {
return err
}
}
return nil
}
// ServerInfo stores details about the supported features and platform of the
// server
type ServerInfo struct {
@ -519,7 +560,7 @@ type ServerInfo struct {
// in the ping response, or if an error occurred, in which case the client
// should use other ways to get the current swarm status, such as the /swarm
// endpoint.
SwarmStatus *swarm.Status
SwarmStatus *client.SwarmStatus
}
// NewDockerCli returns a DockerCli instance with all operators applied on it.
@ -527,15 +568,17 @@ type ServerInfo struct {
// environment.
func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
defaultOps := []CLIOption{
WithContentTrustFromEnv(),
WithDefaultContextStoreConfig(),
WithStandardStreams(),
WithUserAgent(UserAgent()),
}
ops = append(defaultOps, ops...)
cli := &DockerCli{baseCtx: context.Background()}
if err := cli.Apply(ops...); err != nil {
return nil, err
for _, op := range ops {
if err := op(cli); err != nil {
return nil, err
}
}
return cli, nil
}
@ -547,11 +590,11 @@ func getServerHost(hosts []string, defaultToTLS bool) (string, error) {
case 1:
return dopts.ParseHost(defaultToTLS, hosts[0])
default:
return "", errors.New("Specify only one -H")
return "", errors.New("specify only one -H")
}
}
// UserAgent returns the user agent string used for making API requests
// UserAgent returns the default user agent string used for making API requests.
func UserAgent() string {
return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")"
}

View File

@ -3,16 +3,16 @@ package command
import (
"context"
"encoding/csv"
"errors"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/client"
"github.com/moby/moby/client"
"github.com/moby/term"
"github.com/pkg/errors"
)
// CLIOption is a functional argument to apply options to a [DockerCli]. These
@ -75,28 +75,6 @@ func WithErrorStream(err io.Writer) CLIOption {
}
}
// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
func WithContentTrustFromEnv() CLIOption {
return func(cli *DockerCli) error {
cli.contentTrust = false
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
if t, err := strconv.ParseBool(e); t || err != nil {
// treat any other value as true
cli.contentTrust = true
}
}
return nil
}
}
// WithContentTrust enables content trust on a cli.
func WithContentTrust(enabled bool) CLIOption {
return func(cli *DockerCli) error {
cli.contentTrust = enabled
return nil
}
}
// WithDefaultContextStoreConfig configures the cli to use the default context store configuration.
func WithDefaultContextStoreConfig() CLIOption {
return func(cli *DockerCli) error {
@ -180,61 +158,70 @@ const envOverrideHTTPHeaders = "DOCKER_CUSTOM_HEADERS"
// override headers with the same name).
//
// TODO(thaJeztah): this is a client Option, and should be moved to the client. It is non-exported for that reason.
func withCustomHeadersFromEnv() client.Opt {
return func(apiClient *client.Client) error {
value := os.Getenv(envOverrideHTTPHeaders)
if value == "" {
return nil
}
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return invalidParameter(errors.Errorf(
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
envOverrideHTTPHeaders,
func withCustomHeadersFromEnv() (client.Opt, error) {
value := os.Getenv(envOverrideHTTPHeaders)
if value == "" {
return nil, nil
}
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return nil, invalidParameter(fmt.Errorf(
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
envOverrideHTTPHeaders,
))
}
if len(fields) == 0 {
return nil, nil
}
env := map[string]string{}
for _, kv := range fields {
k, v, hasValue := strings.Cut(kv, "=")
// Only strip whitespace in keys; preserve whitespace in values.
k = strings.TrimSpace(k)
if k == "" {
return nil, invalidParameter(fmt.Errorf(
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
envOverrideHTTPHeaders, kv,
))
}
if len(fields) == 0 {
return nil
// We don't currently allow empty key=value pairs, and produce an error.
// This is something we could allow in future (e.g. to read value
// from an environment variable with the same name). In the meantime,
// produce an error to prevent users from depending on this.
if !hasValue {
return nil, invalidParameter(fmt.Errorf(
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
envOverrideHTTPHeaders, kv,
))
}
env := map[string]string{}
for _, kv := range fields {
k, v, hasValue := strings.Cut(kv, "=")
env[http.CanonicalHeaderKey(k)] = v
}
// Only strip whitespace in keys; preserve whitespace in values.
k = strings.TrimSpace(k)
if len(env) == 0 {
// We should probably not hit this case, as we don't skip values
// (only return errors), but we don't want to discard existing
// headers with an empty set.
return nil, nil
}
if k == "" {
return invalidParameter(errors.Errorf(
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
envOverrideHTTPHeaders, kv,
))
}
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
return client.WithHTTPHeaders(env), nil
}
// We don't currently allow empty key=value pairs, and produce an error.
// This is something we could allow in future (e.g. to read value
// from an environment variable with the same name). In the meantime,
// produce an error to prevent users from depending on this.
if !hasValue {
return invalidParameter(errors.Errorf(
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
envOverrideHTTPHeaders, kv,
))
}
env[http.CanonicalHeaderKey(k)] = v
// WithUserAgent configures the User-Agent string for cli HTTP requests.
func WithUserAgent(userAgent string) CLIOption {
return func(cli *DockerCli) error {
if userAgent == "" {
return errors.New("user agent cannot be blank")
}
if len(env) == 0 {
// We should probably not hit this case, as we don't skip values
// (only return errors), but we don't want to discard existing
// headers with an empty set.
return nil
}
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
return client.WithHTTPHeaders(env)(apiClient)
cli.userAgent = userAgent
return nil
}
}

View File

@ -1,28 +0,0 @@
package command
import (
"os"
"testing"
"gotest.tools/v3/assert"
)
func contentTrustEnabled(t *testing.T) bool {
t.Helper()
var cli DockerCli
assert.NilError(t, WithContentTrustFromEnv()(&cli))
return cli.contentTrust
}
// NB: Do not t.Parallel() this test -- it messes with the process environment.
func TestWithContentTrustFromEnv(t *testing.T) {
const envvar = "DOCKER_CONTENT_TRUST"
t.Setenv(envvar, "true")
assert.Check(t, contentTrustEnabled(t))
t.Setenv(envvar, "false")
assert.Check(t, !contentTrustEnabled(t))
t.Setenv(envvar, "invalid")
assert.Check(t, contentTrustEnabled(t))
os.Unsetenv(envvar)
assert.Check(t, !contentTrustEnabled(t))
}

View File

@ -18,10 +18,9 @@ import (
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/context/store"
"github.com/docker/cli/cli/flags"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
)
@ -34,7 +33,7 @@ func TestNewAPIClientFromFlags(t *testing.T) {
apiClient, err := NewAPIClientFromFlags(opts, &configfile.ConfigFile{})
assert.NilError(t, err)
assert.Equal(t, apiClient.DaemonHost(), host)
assert.Equal(t, apiClient.ClientVersion(), api.DefaultVersion)
assert.Equal(t, apiClient.ClientVersion(), client.MaxAPIVersion)
}
func TestNewAPIClientFromFlagsForDefaultSchema(t *testing.T) {
@ -47,7 +46,7 @@ func TestNewAPIClientFromFlagsForDefaultSchema(t *testing.T) {
apiClient, err := NewAPIClientFromFlags(opts, &configfile.ConfigFile{})
assert.NilError(t, err)
assert.Equal(t, apiClient.DaemonHost(), slug+host)
assert.Equal(t, apiClient.ClientVersion(), api.DefaultVersion)
assert.Equal(t, apiClient.ClientVersion(), client.MaxAPIVersion)
}
func TestNewAPIClientFromFlagsWithCustomHeaders(t *testing.T) {
@ -71,7 +70,7 @@ func TestNewAPIClientFromFlagsWithCustomHeaders(t *testing.T) {
apiClient, err := NewAPIClientFromFlags(opts, configFile)
assert.NilError(t, err)
assert.Equal(t, apiClient.DaemonHost(), host)
assert.Equal(t, apiClient.ClientVersion(), api.DefaultVersion)
assert.Equal(t, apiClient.ClientVersion(), client.MaxAPIVersion)
// verify User-Agent is not appended to the configfile. see https://github.com/docker/cli/pull/2756
assert.DeepEqual(t, configFile.HTTPHeaders, map[string]string{"My-Header": "Custom-Value"})
@ -80,7 +79,7 @@ func TestNewAPIClientFromFlagsWithCustomHeaders(t *testing.T) {
"My-Header": "Custom-Value",
"User-Agent": UserAgent(),
}
_, err = apiClient.Ping(context.Background())
_, err = apiClient.Ping(context.TODO(), client.PingOptions{})
assert.NilError(t, err)
assert.DeepEqual(t, received, expectedHeaders)
}
@ -106,7 +105,7 @@ func TestNewAPIClientFromFlagsWithCustomHeadersFromEnv(t *testing.T) {
apiClient, err := NewAPIClientFromFlags(opts, configFile)
assert.NilError(t, err)
assert.Equal(t, apiClient.DaemonHost(), host)
assert.Equal(t, apiClient.ClientVersion(), api.DefaultVersion)
assert.Equal(t, apiClient.ClientVersion(), client.MaxAPIVersion)
expectedHeaders := http.Header{
"One": []string{"one-value"},
@ -115,7 +114,7 @@ func TestNewAPIClientFromFlagsWithCustomHeadersFromEnv(t *testing.T) {
"Four": []string{"four-value-override"},
"User-Agent": []string{UserAgent()},
}
_, err = apiClient.Ping(context.Background())
_, err = apiClient.Ping(context.TODO(), client.PingOptions{})
assert.NilError(t, err)
assert.DeepEqual(t, received, expectedHeaders)
}
@ -135,51 +134,55 @@ func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
type fakeClient struct {
client.Client
pingFunc func() (types.Ping, error)
pingFunc func() (client.PingResult, error)
version string
negotiated bool
}
func (c *fakeClient) Ping(_ context.Context) (types.Ping, error) {
return c.pingFunc()
func (c *fakeClient) Ping(_ context.Context, options client.PingOptions) (client.PingResult, error) {
res, err := c.pingFunc()
if options.NegotiateAPIVersion {
if res.APIVersion != "" {
if c.negotiated || options.ForceNegotiate {
c.negotiated = true
}
}
}
return res, err
}
func (c *fakeClient) ClientVersion() string {
return c.version
}
func (c *fakeClient) NegotiateAPIVersionPing(types.Ping) {
c.negotiated = true
}
func TestInitializeFromClient(t *testing.T) {
const defaultVersion = "v1.55"
testcases := []struct {
doc string
pingFunc func() (types.Ping, error)
pingFunc func() (client.PingResult, error)
expectedServer ServerInfo
negotiated bool
}{
{
doc: "successful ping",
pingFunc: func() (types.Ping, error) {
return types.Ping{Experimental: true, OSType: "linux", APIVersion: "v1.30"}, nil
pingFunc: func() (client.PingResult, error) {
return client.PingResult{Experimental: true, OSType: "linux", APIVersion: "v1.44"}, nil
},
expectedServer: ServerInfo{HasExperimental: true, OSType: "linux"},
negotiated: true,
},
{
doc: "failed ping, no API version",
pingFunc: func() (types.Ping, error) {
return types.Ping{}, errors.New("failed")
pingFunc: func() (client.PingResult, error) {
return client.PingResult{}, errors.New("failed")
},
expectedServer: ServerInfo{HasExperimental: true},
},
{
doc: "failed ping, with API version",
pingFunc: func() (types.Ping, error) {
return types.Ping{APIVersion: "v1.33"}, errors.New("failed")
pingFunc: func() (client.PingResult, error) {
return client.PingResult{APIVersion: "v1.44"}, errors.New("failed")
},
expectedServer: ServerInfo{HasExperimental: true},
negotiated: true,
@ -188,16 +191,16 @@ func TestInitializeFromClient(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.doc, func(t *testing.T) {
apiclient := &fakeClient{
apiClient := &fakeClient{
pingFunc: tc.pingFunc,
version: defaultVersion,
}
cli := &DockerCli{client: apiclient}
cli := &DockerCli{client: apiClient}
err := cli.Initialize(flags.NewClientOptions())
assert.NilError(t, err)
assert.DeepEqual(t, cli.ServerInfo(), tc.expectedServer)
assert.Equal(t, apiclient.negotiated, tc.negotiated)
assert.Equal(t, apiClient.negotiated, tc.negotiated)
})
}
}
@ -211,7 +214,7 @@ func TestInitializeFromClientHangs(t *testing.T) {
assert.NilError(t, err)
receiveReqCh := make(chan bool)
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second)
timeoutCtx, cancel := context.WithTimeout(context.TODO(), time.Second)
defer cancel()
// Simulate a server that hangs on connections.
@ -255,8 +258,14 @@ func TestInitializeFromClientHangs(t *testing.T) {
}
func TestNewDockerCliAndOperators(t *testing.T) {
// Test default operations and also overriding default ones
cli, err := NewDockerCli(WithInputStream(io.NopCloser(strings.NewReader("some input"))))
outbuf := bytes.NewBuffer(nil)
errbuf := bytes.NewBuffer(nil)
cli, err := NewDockerCli(
WithInputStream(io.NopCloser(strings.NewReader("some input"))),
WithOutputStream(outbuf),
WithErrorStream(errbuf),
)
assert.NilError(t, err)
// Check streams are initialized
assert.Check(t, cli.In() != nil)
@ -266,19 +275,6 @@ func TestNewDockerCliAndOperators(t *testing.T) {
assert.NilError(t, err)
assert.Equal(t, string(inputStream), "some input")
// Apply can modify a dockerCli after construction
outbuf := bytes.NewBuffer(nil)
errbuf := bytes.NewBuffer(nil)
err = cli.Apply(
WithInputStream(io.NopCloser(strings.NewReader("input"))),
WithOutputStream(outbuf),
WithErrorStream(errbuf),
)
assert.NilError(t, err)
// Check input stream
inputStream, err = io.ReadAll(cli.In())
assert.NilError(t, err)
assert.Equal(t, string(inputStream), "input")
// Check output stream
_, err = fmt.Fprint(cli.Out(), "output")
assert.NilError(t, err)
@ -296,9 +292,9 @@ func TestNewDockerCliAndOperators(t *testing.T) {
func TestInitializeShouldAlwaysCreateTheContextStore(t *testing.T) {
cli, err := NewDockerCli()
assert.NilError(t, err)
assert.NilError(t, cli.Initialize(flags.NewClientOptions(), WithInitializeClient(func(cli *DockerCli) (client.APIClient, error) {
return client.NewClientWithOpts()
})))
apiClient, err := client.New()
assert.NilError(t, err)
assert.NilError(t, cli.Initialize(flags.NewClientOptions(), WithAPIClient(apiClient)))
assert.Check(t, cli.ContextStore() != nil)
}
@ -353,3 +349,46 @@ func TestHooksEnabled(t *testing.T) {
assert.Check(t, !cli.HooksEnabled())
})
}
func TestSetGoDebug(t *testing.T) {
t.Run("GODEBUG already set", func(t *testing.T) {
t.Setenv("GODEBUG", "val1,val2")
meta := store.Metadata{}
setGoDebug(meta)
assert.Equal(t, "val1,val2", os.Getenv("GODEBUG"))
})
t.Run("GODEBUG in context metadata can set env", func(t *testing.T) {
meta := store.Metadata{
Metadata: DockerContext{
AdditionalFields: map[string]any{
"GODEBUG": "val1,val2=1",
},
},
}
setGoDebug(meta)
assert.Equal(t, "val1,val2=1", os.Getenv("GODEBUG"))
})
}
func TestNewDockerCliWithCustomUserAgent(t *testing.T) {
var received string
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
received = r.UserAgent()
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()
host := strings.Replace(ts.URL, "http://", "tcp://", 1)
opts := &flags.ClientOptions{Hosts: []string{host}}
cli, err := NewDockerCli(
WithUserAgent("fake-agent/0.0.1"),
)
assert.NilError(t, err)
cli.currentContext = DefaultContextName
cli.options = opts
cli.configFile = &configfile.ConfigFile{}
_, err = cli.Client().Ping(context.TODO(), client.PingOptions{})
assert.NilError(t, err)
assert.DeepEqual(t, received, "fake-agent/0.0.1")
}

View File

@ -1,110 +1,30 @@
package commands
import (
"os"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/builder"
"github.com/docker/cli/cli/command/checkpoint"
"github.com/docker/cli/cli/command/config"
"github.com/docker/cli/cli/command/container"
"github.com/docker/cli/cli/command/context"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/command/manifest"
"github.com/docker/cli/cli/command/network"
"github.com/docker/cli/cli/command/node"
"github.com/docker/cli/cli/command/plugin"
"github.com/docker/cli/cli/command/registry"
"github.com/docker/cli/cli/command/secret"
"github.com/docker/cli/cli/command/service"
"github.com/docker/cli/cli/command/stack"
"github.com/docker/cli/cli/command/swarm"
"github.com/docker/cli/cli/command/system"
"github.com/docker/cli/cli/command/trust"
"github.com/docker/cli/cli/command/volume"
_ "github.com/docker/cli/cli/command/builder"
_ "github.com/docker/cli/cli/command/checkpoint"
_ "github.com/docker/cli/cli/command/config"
_ "github.com/docker/cli/cli/command/container"
_ "github.com/docker/cli/cli/command/context"
_ "github.com/docker/cli/cli/command/image"
_ "github.com/docker/cli/cli/command/manifest"
_ "github.com/docker/cli/cli/command/network"
_ "github.com/docker/cli/cli/command/node"
_ "github.com/docker/cli/cli/command/plugin"
_ "github.com/docker/cli/cli/command/registry"
_ "github.com/docker/cli/cli/command/secret"
_ "github.com/docker/cli/cli/command/service"
_ "github.com/docker/cli/cli/command/stack"
_ "github.com/docker/cli/cli/command/swarm"
_ "github.com/docker/cli/cli/command/system"
_ "github.com/docker/cli/cli/command/volume"
"github.com/docker/cli/internal/commands"
"github.com/spf13/cobra"
)
// AddCommands adds all the commands from cli/command to the root command
func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
cmd.AddCommand(
// commonly used shorthands
container.NewRunCommand(dockerCli),
container.NewExecCommand(dockerCli),
container.NewPsCommand(dockerCli),
image.NewBuildCommand(dockerCli),
image.NewPullCommand(dockerCli),
image.NewPushCommand(dockerCli),
image.NewImagesCommand(dockerCli),
registry.NewLoginCommand(dockerCli),
registry.NewLogoutCommand(dockerCli),
registry.NewSearchCommand(dockerCli),
system.NewVersionCommand(dockerCli),
system.NewInfoCommand(dockerCli),
// management commands
builder.NewBakeStubCommand(dockerCli),
builder.NewBuilderCommand(dockerCli),
checkpoint.NewCheckpointCommand(dockerCli),
container.NewContainerCommand(dockerCli),
context.NewContextCommand(dockerCli),
image.NewImageCommand(dockerCli),
manifest.NewManifestCommand(dockerCli),
network.NewNetworkCommand(dockerCli),
plugin.NewPluginCommand(dockerCli),
system.NewSystemCommand(dockerCli),
trust.NewTrustCommand(dockerCli),
volume.NewVolumeCommand(dockerCli),
// orchestration (swarm) commands
config.NewConfigCommand(dockerCli),
node.NewNodeCommand(dockerCli),
secret.NewSecretCommand(dockerCli),
service.NewServiceCommand(dockerCli),
stack.NewStackCommand(dockerCli),
swarm.NewSwarmCommand(dockerCli),
// legacy commands may be hidden
hide(container.NewAttachCommand(dockerCli)),
hide(container.NewCommitCommand(dockerCli)),
hide(container.NewCopyCommand(dockerCli)),
hide(container.NewCreateCommand(dockerCli)),
hide(container.NewDiffCommand(dockerCli)),
hide(container.NewExportCommand(dockerCli)),
hide(container.NewKillCommand(dockerCli)),
hide(container.NewLogsCommand(dockerCli)),
hide(container.NewPauseCommand(dockerCli)),
hide(container.NewPortCommand(dockerCli)),
hide(container.NewRenameCommand(dockerCli)),
hide(container.NewRestartCommand(dockerCli)),
hide(container.NewRmCommand(dockerCli)),
hide(container.NewStartCommand(dockerCli)),
hide(container.NewStatsCommand(dockerCli)),
hide(container.NewStopCommand(dockerCli)),
hide(container.NewTopCommand(dockerCli)),
hide(container.NewUnpauseCommand(dockerCli)),
hide(container.NewUpdateCommand(dockerCli)),
hide(container.NewWaitCommand(dockerCli)),
hide(image.NewHistoryCommand(dockerCli)),
hide(image.NewImportCommand(dockerCli)),
hide(image.NewLoadCommand(dockerCli)),
hide(image.NewRemoveCommand(dockerCli)),
hide(image.NewSaveCommand(dockerCli)),
hide(image.NewTagCommand(dockerCli)),
hide(system.NewEventsCommand(dockerCli)),
hide(system.NewInspectCommand(dockerCli)),
)
}
func hide(cmd *cobra.Command) *cobra.Command {
// If the environment variable with name "DOCKER_HIDE_LEGACY_COMMANDS" is not empty,
// these legacy commands (such as `docker ps`, `docker exec`, etc)
// will not be shown in output console.
if os.Getenv("DOCKER_HIDE_LEGACY_COMMANDS") == "" {
return cmd
func AddCommands(cmd *cobra.Command, dockerCLI command.Cli) {
for _, c := range commands.Commands() {
cmd.AddCommand(c(dockerCLI))
}
cmdCopy := *cmd
cmdCopy.Hidden = true
cmdCopy.Aliases = []string{}
return &cmdCopy
}

View File

@ -4,21 +4,14 @@ import (
"os"
"strings"
"github.com/distribution/reference"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
// ValidArgsFn a function to be used by cobra command as `ValidArgsFunction` to offer command line completion.
//
// Deprecated: use [cobra.CompletionFunc].
type ValidArgsFn = cobra.CompletionFunc
// APIClientProvider provides a method to get an [client.APIClient], initializing
// APIClientProvider provides a method to get a [client.APIClient], initializing
// it if needed.
//
// It's a smaller interface than [command.Cli], and used in situations where an
@ -34,24 +27,57 @@ func ImageNames(dockerCLI APIClientProvider, limit int) cobra.CompletionFunc {
if limit > 0 && len(args) >= limit {
return nil, cobra.ShellCompDirectiveNoFileComp
}
list, err := dockerCLI.Client().ImageList(cmd.Context(), image.ListOptions{})
res, err := dockerCLI.Client().ImageList(cmd.Context(), client.ImageListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, img := range list {
for _, img := range res.Items {
names = append(names, img.RepoTags...)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
}
// ImageNamesWithBase offers completion for images present within the local store,
// including both full image names with tags and base image names (repository names only)
// when multiple tags exist for the same base name
func ImageNamesWithBase(dockerCLI APIClientProvider, limit int) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if limit > 0 && len(args) >= limit {
return nil, cobra.ShellCompDirectiveNoFileComp
}
res, err := dockerCLI.Client().ImageList(cmd.Context(), client.ImageListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
baseNameCounts := make(map[string]int)
for _, img := range res.Items {
names = append(names, img.RepoTags...)
for _, tag := range img.RepoTags {
ref, err := reference.ParseNormalizedNamed(tag)
if err != nil {
continue
}
baseNameCounts[reference.FamiliarName(ref)]++
}
}
for baseName, count := range baseNameCounts {
if count > 1 {
names = append(names, baseName)
}
}
return names, cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
}
}
// ContainerNames offers completion for container names and IDs
// By default, only names are returned.
// Set DOCKER_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs.
func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(container.Summary) bool) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCLI.Client().ContainerList(cmd.Context(), container.ListOptions{
res, err := dockerCLI.Client().ContainerList(cmd.Context(), client.ContainerListOptions{
All: all,
})
if err != nil {
@ -61,7 +87,7 @@ func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(conta
showContainerIDs := os.Getenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS") == "yes"
var names []string
for _, ctr := range list {
for _, ctr := range res.Items {
skip := false
for _, fn := range filters {
if fn != nil && !fn(ctr) {
@ -84,12 +110,12 @@ func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(conta
// VolumeNames offers completion for volumes
func VolumeNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCLI.Client().VolumeList(cmd.Context(), volume.ListOptions{})
res, err := dockerCLI.Client().VolumeList(cmd.Context(), client.VolumeListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, vol := range list.Volumes {
for _, vol := range res.Items {
names = append(names, vol.Name)
}
return names, cobra.ShellCompDirectiveNoFileComp
@ -99,12 +125,12 @@ func VolumeNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
// NetworkNames offers completion for networks
func NetworkNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCLI.Client().NetworkList(cmd.Context(), network.ListOptions{})
res, err := dockerCLI.Client().NetworkList(cmd.Context(), client.NetworkListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, nw := range list {
for _, nw := range res.Items {
names = append(names, nw.Name)
}
return names, cobra.ShellCompDirectiveNoFileComp
@ -124,14 +150,16 @@ func NetworkNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
// export MY_VAR=hello
// docker run --rm --env MY_VAR alpine printenv MY_VAR
// hello
func EnvVarNames(_ *cobra.Command, _ []string, _ string) (names []string, _ cobra.ShellCompDirective) {
envs := os.Environ()
names = make([]string, 0, len(envs))
for _, env := range envs {
name, _, _ := strings.Cut(env, "=")
names = append(names, name)
func EnvVarNames() cobra.CompletionFunc {
return func(_ *cobra.Command, _ []string, _ string) (names []string, _ cobra.ShellCompDirective) {
envs := os.Environ()
names = make([]string, 0, len(envs))
for _, env := range envs {
name, _, _ := strings.Cut(env, "=")
names = append(names, name)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
return names, cobra.ShellCompDirectiveNoFileComp
}
// FromList offers completion for the given list of options.
@ -142,13 +170,10 @@ func FromList(options ...string) cobra.CompletionFunc {
// FileNames is a convenience function to use [cobra.ShellCompDirectiveDefault],
// which indicates to let the shell perform its default behavior after
// completions have been provided.
func FileNames(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault
}
// NoComplete is used for commands where there's no relevant completion
func NoComplete(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveNoFileComp
func FileNames() cobra.CompletionFunc {
return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault
}
}
var commonPlatforms = []string{
@ -188,6 +213,8 @@ var commonPlatforms = []string{
// - we currently exclude architectures that may have unofficial builds,
// but don't have wide adoption (and no support), such as loong64, mipsXXX,
// ppc64 (non-le) to prevent confusion.
func Platforms(_ *cobra.Command, _ []string, _ string) (platforms []string, _ cobra.ShellCompDirective) {
return commonPlatforms, cobra.ShellCompDirectiveNoFileComp
func Platforms() cobra.CompletionFunc {
return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return commonPlatforms, cobra.ShellCompDirectiveNoFileComp
}
}

View File

@ -6,13 +6,11 @@ import (
"sort"
"testing"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/volume"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -30,38 +28,38 @@ func (c fakeCLI) Client() client.APIClient {
type fakeClient struct {
client.Client
containerListFunc func(options container.ListOptions) ([]container.Summary, error)
imageListFunc func(options image.ListOptions) ([]image.Summary, error)
networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error)
volumeListFunc func(filter filters.Args) (volume.ListResponse, error)
containerListFunc func(context.Context, client.ContainerListOptions) (client.ContainerListResult, error)
imageListFunc func(context.Context, client.ImageListOptions) (client.ImageListResult, error)
networkListFunc func(context.Context, client.NetworkListOptions) (client.NetworkListResult, error)
volumeListFunc func(context.Context, client.VolumeListOptions) (client.VolumeListResult, error)
}
func (c *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]container.Summary, error) {
func (c *fakeClient) ContainerList(ctx context.Context, options client.ContainerListOptions) (client.ContainerListResult, error) {
if c.containerListFunc != nil {
return c.containerListFunc(options)
return c.containerListFunc(ctx, options)
}
return []container.Summary{}, nil
return client.ContainerListResult{}, nil
}
func (c *fakeClient) ImageList(_ context.Context, options image.ListOptions) ([]image.Summary, error) {
func (c *fakeClient) ImageList(ctx context.Context, options client.ImageListOptions) (client.ImageListResult, error) {
if c.imageListFunc != nil {
return c.imageListFunc(options)
return c.imageListFunc(ctx, options)
}
return []image.Summary{}, nil
return client.ImageListResult{}, nil
}
func (c *fakeClient) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
func (c *fakeClient) NetworkList(ctx context.Context, options client.NetworkListOptions) (client.NetworkListResult, error) {
if c.networkListFunc != nil {
return c.networkListFunc(ctx, options)
}
return []network.Inspect{}, nil
return client.NetworkListResult{}, nil
}
func (c *fakeClient) VolumeList(_ context.Context, options volume.ListOptions) (volume.ListResponse, error) {
func (c *fakeClient) VolumeList(ctx context.Context, options client.VolumeListOptions) (client.VolumeListResult, error) {
if c.volumeListFunc != nil {
return c.volumeListFunc(options.Filters)
return c.volumeListFunc(ctx, options)
}
return volume.ListResponse{}, nil
return client.VolumeListResult{}, nil
}
func TestCompleteContainerNames(t *testing.T) {
@ -71,7 +69,7 @@ func TestCompleteContainerNames(t *testing.T) {
filters []func(container.Summary) bool
containers []container.Summary
expOut []string
expOpts container.ListOptions
expOpts client.ContainerListOptions
expDirective cobra.ShellCompDirective
}{
{
@ -87,7 +85,7 @@ func TestCompleteContainerNames(t *testing.T) {
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
},
expOut: []string{"container-c", "container-c/link-b", "container-b", "container-a"},
expOpts: container.ListOptions{All: true},
expOpts: client.ContainerListOptions{All: true},
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
@ -100,7 +98,7 @@ func TestCompleteContainerNames(t *testing.T) {
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
},
expOut: []string{"id-c", "container-c", "container-c/link-b", "id-b", "container-b", "id-a", "container-a"},
expOpts: container.ListOptions{All: true},
expOpts: client.ContainerListOptions{All: true},
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
@ -124,7 +122,7 @@ func TestCompleteContainerNames(t *testing.T) {
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
},
expOut: []string{"container-b"},
expOpts: container.ListOptions{All: true},
expOpts: client.ContainerListOptions{All: true},
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
@ -140,7 +138,7 @@ func TestCompleteContainerNames(t *testing.T) {
{ID: "id-a", State: container.StateCreated, Names: []string{"/container-a"}},
},
expOut: []string{"container-a"},
expOpts: container.ListOptions{All: true},
expOpts: client.ContainerListOptions{All: true},
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
@ -155,12 +153,12 @@ func TestCompleteContainerNames(t *testing.T) {
t.Setenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS", "yes")
}
comp := ContainerNames(fakeCLI{&fakeClient{
containerListFunc: func(opts container.ListOptions) ([]container.Summary, error) {
assert.Check(t, is.DeepEqual(opts, tc.expOpts, cmpopts.IgnoreUnexported(container.ListOptions{}, filters.Args{})))
containerListFunc: func(_ context.Context, opts client.ContainerListOptions) (client.ContainerListResult, error) {
assert.Check(t, is.DeepEqual(opts, tc.expOpts))
if tc.expDirective == cobra.ShellCompDirectiveError {
return nil, errors.New("some error occurred")
return client.ContainerListResult{}, errors.New("some error occurred")
}
return tc.containers, nil
return client.ContainerListResult{Items: tc.containers}, nil
},
}}, tc.showAll, tc.filters...)
@ -176,7 +174,7 @@ func TestCompleteEnvVarNames(t *testing.T) {
"ENV_A": "hello-a",
"ENV_B": "hello-b",
})
values, directives := EnvVarNames(nil, nil, "")
values, directives := EnvVarNames()(nil, nil, "")
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
sort.Strings(values)
@ -185,7 +183,7 @@ func TestCompleteEnvVarNames(t *testing.T) {
}
func TestCompleteFileNames(t *testing.T) {
values, directives := FileNames(nil, nil, "")
values, directives := FileNames()(nil, nil, "")
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveDefault))
assert.Check(t, is.Len(values, 0))
}
@ -228,11 +226,11 @@ func TestCompleteImageNames(t *testing.T) {
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
comp := ImageNames(fakeCLI{&fakeClient{
imageListFunc: func(options image.ListOptions) ([]image.Summary, error) {
imageListFunc: func(context.Context, client.ImageListOptions) (client.ImageListResult, error) {
if tc.expDirective == cobra.ShellCompDirectiveError {
return nil, errors.New("some error occurred")
return client.ImageListResult{}, errors.New("some error occurred")
}
return tc.images, nil
return client.ImageListResult{Items: tc.images}, nil
},
}}, -1)
@ -257,9 +255,24 @@ func TestCompleteNetworkNames(t *testing.T) {
{
doc: "with results",
networks: []network.Summary{
{ID: "nw-c", Name: "network-c"},
{ID: "nw-b", Name: "network-b"},
{ID: "nw-a", Name: "network-a"},
{
Network: network.Network{
ID: "nw-c",
Name: "network-c",
},
},
{
Network: network.Network{
ID: "nw-b",
Name: "network-b",
},
},
{
Network: network.Network{
ID: "nw-a",
Name: "network-a",
},
},
},
expOut: []string{"network-c", "network-b", "network-a"},
expDirective: cobra.ShellCompDirectiveNoFileComp,
@ -273,11 +286,11 @@ func TestCompleteNetworkNames(t *testing.T) {
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
comp := NetworkNames(fakeCLI{&fakeClient{
networkListFunc: func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
networkListFunc: func(context.Context, client.NetworkListOptions) (client.NetworkListResult, error) {
if tc.expDirective == cobra.ShellCompDirectiveError {
return nil, errors.New("some error occurred")
return client.NetworkListResult{}, errors.New("some error occurred")
}
return tc.networks, nil
return client.NetworkListResult{Items: tc.networks}, nil
},
}})
@ -288,14 +301,8 @@ func TestCompleteNetworkNames(t *testing.T) {
}
}
func TestCompleteNoComplete(t *testing.T) {
values, directives := NoComplete(nil, nil, "")
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp))
assert.Check(t, is.Len(values, 0))
}
func TestCompletePlatforms(t *testing.T) {
values, directives := Platforms(nil, nil, "")
values, directives := Platforms()(nil, nil, "")
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
assert.Check(t, is.DeepEqual(values, commonPlatforms))
}
@ -303,7 +310,7 @@ func TestCompletePlatforms(t *testing.T) {
func TestCompleteVolumeNames(t *testing.T) {
tests := []struct {
doc string
volumes []*volume.Volume
volumes []volume.Volume
expOut []string
expDirective cobra.ShellCompDirective
}{
@ -313,7 +320,7 @@ func TestCompleteVolumeNames(t *testing.T) {
},
{
doc: "with results",
volumes: []*volume.Volume{
volumes: []volume.Volume{
{Name: "volume-c"},
{Name: "volume-b"},
{Name: "volume-a"},
@ -330,11 +337,11 @@ func TestCompleteVolumeNames(t *testing.T) {
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
comp := VolumeNames(fakeCLI{&fakeClient{
volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) {
volumeListFunc: func(context.Context, client.VolumeListOptions) (client.VolumeListResult, error) {
if tc.expDirective == cobra.ShellCompDirectiveError {
return volume.ListResponse{}, errors.New("some error occurred")
return client.VolumeListResult{}, errors.New("some error occurred")
}
return volume.ListResponse{Volumes: tc.volumes}, nil
return client.VolumeListResult{Items: tc.volumes}, nil
},
}})

View File

@ -3,42 +3,41 @@ package config
import (
"context"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/moby/moby/client"
)
type fakeClient struct {
client.Client
configCreateFunc func(context.Context, swarm.ConfigSpec) (swarm.ConfigCreateResponse, error)
configInspectFunc func(context.Context, string) (swarm.Config, []byte, error)
configListFunc func(context.Context, swarm.ConfigListOptions) ([]swarm.Config, error)
configRemoveFunc func(string) error
configCreateFunc func(context.Context, client.ConfigCreateOptions) (client.ConfigCreateResult, error)
configInspectFunc func(context.Context, string, client.ConfigInspectOptions) (client.ConfigInspectResult, error)
configListFunc func(context.Context, client.ConfigListOptions) (client.ConfigListResult, error)
configRemoveFunc func(context.Context, string, client.ConfigRemoveOptions) (client.ConfigRemoveResult, error)
}
func (c *fakeClient) ConfigCreate(ctx context.Context, spec swarm.ConfigSpec) (swarm.ConfigCreateResponse, error) {
func (c *fakeClient) ConfigCreate(ctx context.Context, options client.ConfigCreateOptions) (client.ConfigCreateResult, error) {
if c.configCreateFunc != nil {
return c.configCreateFunc(ctx, spec)
return c.configCreateFunc(ctx, options)
}
return swarm.ConfigCreateResponse{}, nil
return client.ConfigCreateResult{}, nil
}
func (c *fakeClient) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) {
func (c *fakeClient) ConfigInspect(ctx context.Context, id string, options client.ConfigInspectOptions) (client.ConfigInspectResult, error) {
if c.configInspectFunc != nil {
return c.configInspectFunc(ctx, id)
return c.configInspectFunc(ctx, id, options)
}
return swarm.Config{}, nil, nil
return client.ConfigInspectResult{}, nil
}
func (c *fakeClient) ConfigList(ctx context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
func (c *fakeClient) ConfigList(ctx context.Context, options client.ConfigListOptions) (client.ConfigListResult, error) {
if c.configListFunc != nil {
return c.configListFunc(ctx, options)
}
return []swarm.Config{}, nil
return client.ConfigListResult{}, nil
}
func (c *fakeClient) ConfigRemove(_ context.Context, name string) error {
func (c *fakeClient) ConfigRemove(ctx context.Context, name string, options client.ConfigRemoveOptions) (client.ConfigRemoveResult, error) {
if c.configRemoveFunc != nil {
return c.configRemoveFunc(name)
return c.configRemoveFunc(ctx, name, options)
}
return nil
return client.ConfigRemoveResult{}, nil
}

View File

@ -4,27 +4,33 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/cli/internal/commands"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
// NewConfigCommand returns a cobra command for `config` subcommands
func NewConfigCommand(dockerCli command.Cli) *cobra.Command {
func init() {
commands.Register(newConfigCommand)
}
// newConfigCommand returns a cobra command for `config` subcommands
func newConfigCommand(dockerCLI command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Manage Swarm configs",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
RunE: command.ShowHelp(dockerCLI.Err()),
Annotations: map[string]string{
"version": "1.30",
"swarm": "manager",
},
DisableFlagsInUseLine: true,
}
cmd.AddCommand(
newConfigListCommand(dockerCli),
newConfigCreateCommand(dockerCli),
newConfigInspectCommand(dockerCli),
newConfigRemoveCommand(dockerCli),
newConfigListCommand(dockerCLI),
newConfigCreateCommand(dockerCLI),
newConfigInspectCommand(dockerCLI),
newConfigRemoveCommand(dockerCLI),
)
return cmd
}
@ -32,12 +38,12 @@ func NewConfigCommand(dockerCli command.Cli) *cobra.Command {
// completeNames offers completion for swarm configs
func completeNames(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCLI.Client().ConfigList(cmd.Context(), swarm.ConfigListOptions{})
res, err := dockerCLI.Client().ConfigList(cmd.Context(), client.ConfigListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, config := range list {
for _, config := range res.Items {
names = append(names, config.ID)
}
return names, cobra.ShellCompDirectiveNoFileComp

View File

@ -2,30 +2,30 @@ package config
import (
"context"
"errors"
"fmt"
"io"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/swarm"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/moby/sys/sequential"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// CreateOptions specifies some options that are used when creating a config.
type CreateOptions struct {
Name string
TemplateDriver string
File string
Labels opts.ListOpts
// createOptions specifies some options that are used when creating a config.
type createOptions struct {
name string
templateDriver string
file string
labels opts.ListOpts
}
func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command {
createOpts := CreateOptions{
Labels: opts.NewListOpts(opts.ValidateLabel),
func newConfigCreateCommand(dockerCLI command.Cli) *cobra.Command {
createOpts := createOptions{
labels: opts.NewListOpts(opts.ValidateLabel),
}
cmd := &cobra.Command{
@ -33,42 +33,61 @@ func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command {
Short: "Create a config from a file or STDIN",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
createOpts.Name = args[0]
createOpts.File = args[1]
return RunConfigCreate(cmd.Context(), dockerCli, createOpts)
createOpts.name = args[0]
createOpts.file = args[1]
return runCreate(cmd.Context(), dockerCLI, createOpts)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
switch len(args) {
case 0:
// No completion for the first argument, which is the name for
// the new config, but if a non-empty name is given, we return
// it as completion to allow "tab"-ing to the next completion.
return []string{toComplete}, cobra.ShellCompDirectiveNoFileComp
case 1:
// Second argument is either "-" or a file to load.
//
// TODO(thaJeztah): provide completion for "-".
return nil, cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveDefault
default:
// Command only accepts two arguments.
return nil, cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
}
},
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
flags.VarP(&createOpts.Labels, "label", "l", "Config labels")
flags.StringVar(&createOpts.TemplateDriver, "template-driver", "", "Template driver")
flags.SetAnnotation("template-driver", "version", []string{"1.37"})
flags.VarP(&createOpts.labels, "label", "l", "Config labels")
flags.StringVar(&createOpts.templateDriver, "template-driver", "", "Template driver")
_ = flags.SetAnnotation("template-driver", "version", []string{"1.37"})
return cmd
}
// RunConfigCreate creates a config with the given options.
func RunConfigCreate(ctx context.Context, dockerCLI command.Cli, options CreateOptions) error {
// runCreate creates a config with the given options.
func runCreate(ctx context.Context, dockerCLI command.Cli, options createOptions) error {
apiClient := dockerCLI.Client()
configData, err := readConfigData(dockerCLI.In(), options.File)
configData, err := readConfigData(dockerCLI.In(), options.file)
if err != nil {
return errors.Errorf("Error reading content from %q: %v", options.File, err)
return fmt.Errorf("error reading content from %q: %v", options.file, err)
}
spec := swarm.ConfigSpec{
Annotations: swarm.Annotations{
Name: options.Name,
Labels: opts.ConvertKVStringsToMap(options.Labels.GetSlice()),
Name: options.name,
Labels: opts.ConvertKVStringsToMap(options.labels.GetSlice()),
},
Data: configData,
}
if options.TemplateDriver != "" {
if options.templateDriver != "" {
spec.Templating = &swarm.Driver{
Name: options.TemplateDriver,
Name: options.templateDriver,
}
}
r, err := apiClient.ConfigCreate(ctx, spec)
r, err := apiClient.ConfigCreate(ctx, client.ConfigCreateOptions{
Spec: spec,
})
if err != nil {
return err
}

View File

@ -12,7 +12,8 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/swarm"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
@ -23,7 +24,7 @@ const configDataFile = "config-create-with-name.golden"
func TestConfigCreateErrors(t *testing.T) {
testCases := []struct {
args []string
configCreateFunc func(context.Context, swarm.ConfigSpec) (swarm.ConfigCreateResponse, error)
configCreateFunc func(context.Context, client.ConfigCreateOptions) (client.ConfigCreateResult, error)
expectedError string
}{
{
@ -36,8 +37,8 @@ func TestConfigCreateErrors(t *testing.T) {
},
{
args: []string{"name", filepath.Join("testdata", configDataFile)},
configCreateFunc: func(_ context.Context, configSpec swarm.ConfigSpec) (swarm.ConfigCreateResponse, error) {
return swarm.ConfigCreateResponse{}, errors.New("error creating config")
configCreateFunc: func(_ context.Context, options client.ConfigCreateOptions) (client.ConfigCreateResult, error) {
return client.ConfigCreateResult{}, errors.New("error creating config")
},
expectedError: "error creating config",
},
@ -61,15 +62,15 @@ func TestConfigCreateWithName(t *testing.T) {
const name = "config-with-name"
var actual []byte
cli := test.NewFakeCli(&fakeClient{
configCreateFunc: func(_ context.Context, spec swarm.ConfigSpec) (swarm.ConfigCreateResponse, error) {
if spec.Name != name {
return swarm.ConfigCreateResponse{}, fmt.Errorf("expected name %q, got %q", name, spec.Name)
configCreateFunc: func(_ context.Context, options client.ConfigCreateOptions) (client.ConfigCreateResult, error) {
if options.Spec.Name != name {
return client.ConfigCreateResult{}, fmt.Errorf("expected name %q, got %q", name, options.Spec.Name)
}
actual = spec.Data
actual = options.Spec.Data
return swarm.ConfigCreateResponse{
ID: "ID-" + spec.Name,
return client.ConfigCreateResult{
ID: "ID-" + options.Spec.Name,
}, nil
},
})
@ -100,13 +101,13 @@ func TestConfigCreateWithLabels(t *testing.T) {
}
cli := test.NewFakeCli(&fakeClient{
configCreateFunc: func(_ context.Context, spec swarm.ConfigSpec) (swarm.ConfigCreateResponse, error) {
if !reflect.DeepEqual(spec, expected) {
return swarm.ConfigCreateResponse{}, fmt.Errorf("expected %+v, got %+v", expected, spec)
configCreateFunc: func(_ context.Context, options client.ConfigCreateOptions) (client.ConfigCreateResult, error) {
if !reflect.DeepEqual(options.Spec, expected) {
return client.ConfigCreateResult{}, fmt.Errorf("expected %+v, got %+v", expected, options.Spec)
}
return swarm.ConfigCreateResponse{
ID: "ID-" + spec.Name,
return client.ConfigCreateResult{
ID: "ID-" + options.Spec.Name,
}, nil
},
})
@ -126,17 +127,17 @@ func TestConfigCreateWithTemplatingDriver(t *testing.T) {
const name = "config-with-template-driver"
cli := test.NewFakeCli(&fakeClient{
configCreateFunc: func(_ context.Context, spec swarm.ConfigSpec) (swarm.ConfigCreateResponse, error) {
if spec.Name != name {
return swarm.ConfigCreateResponse{}, fmt.Errorf("expected name %q, got %q", name, spec.Name)
configCreateFunc: func(_ context.Context, options client.ConfigCreateOptions) (client.ConfigCreateResult, error) {
if options.Spec.Name != name {
return client.ConfigCreateResult{}, fmt.Errorf("expected name %q, got %q", name, options.Spec.Name)
}
if spec.Templating.Name != expectedDriver.Name {
return swarm.ConfigCreateResponse{}, fmt.Errorf("expected driver %v, got %v", expectedDriver, spec.Labels)
if options.Spec.Templating.Name != expectedDriver.Name {
return client.ConfigCreateResult{}, fmt.Errorf("expected driver %v, got %v", expectedDriver, options.Spec.Labels)
}
return swarm.ConfigCreateResponse{
ID: "ID-" + spec.Name,
return client.ConfigCreateResult{
ID: "ID-" + options.Spec.Name,
}, nil
},
})

View File

@ -7,8 +7,9 @@ import (
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/inspect"
"github.com/docker/docker/api/types/swarm"
units "github.com/docker/go-units"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
)
const (
@ -29,8 +30,8 @@ Data:
{{.Data}}`
)
// NewFormat returns a Format for rendering using a config Context
func NewFormat(source string, quiet bool) formatter.Format {
// newFormat returns a Format for rendering using a configContext.
func newFormat(source string, quiet bool) formatter.Format {
switch source {
case formatter.PrettyFormatKey:
return configInspectPrettyTemplate
@ -43,31 +44,28 @@ func NewFormat(source string, quiet bool) formatter.Format {
return formatter.Format(source)
}
// FormatWrite writes the context
func FormatWrite(ctx formatter.Context, configs []swarm.Config) error {
render := func(format func(subContext formatter.SubContext) error) error {
for _, config := range configs {
// formatWrite writes the context
func formatWrite(fmtCtx formatter.Context, configs client.ConfigListResult) error {
cCtx := &configContext{
HeaderContext: formatter.HeaderContext{
Header: formatter.SubHeaderContext{
"ID": configIDHeader,
"Name": formatter.NameHeader,
"CreatedAt": configCreatedHeader,
"UpdatedAt": configUpdatedHeader,
"Labels": formatter.LabelsHeader,
},
},
}
return fmtCtx.Write(cCtx, func(format func(subContext formatter.SubContext) error) error {
for _, config := range configs.Items {
configCtx := &configContext{c: config}
if err := format(configCtx); err != nil {
return err
}
}
return nil
}
return ctx.Write(newConfigContext(), render)
}
func newConfigContext() *configContext {
cCtx := &configContext{}
cCtx.Header = formatter.SubHeaderContext{
"ID": configIDHeader,
"Name": formatter.NameHeader,
"CreatedAt": configCreatedHeader,
"UpdatedAt": configUpdatedHeader,
"Labels": formatter.LabelsHeader,
}
return cCtx
})
}
type configContext struct {
@ -114,12 +112,12 @@ func (c *configContext) Label(name string) string {
return c.c.Spec.Annotations.Labels[name]
}
// InspectFormatWrite renders the context for a list of configs
func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.GetRefFunc) error {
if ctx.Format != configInspectPrettyTemplate {
return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef)
// inspectFormatWrite renders the context for a list of configs
func inspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef inspect.GetRefFunc) error {
if fmtCtx.Format != configInspectPrettyTemplate {
return inspect.Inspect(fmtCtx.Output, refs, string(fmtCtx.Format), getRef)
}
render := func(format func(subContext formatter.SubContext) error) error {
return fmtCtx.Write(&configInspectContext{}, func(format func(subContext formatter.SubContext) error) error {
for _, ref := range refs {
configI, _, err := getRef(ref)
if err != nil {
@ -134,8 +132,7 @@ func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.Get
}
}
return nil
}
return ctx.Write(&configInspectContext{}, render)
})
}
type configInspectContext struct {

View File

@ -6,7 +6,8 @@ import (
"time"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/swarm"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
)
@ -27,44 +28,46 @@ func TestConfigContextFormatWrite(t *testing.T) {
},
// Table format
{
formatter.Context{Format: NewFormat("table", false)},
formatter.Context{Format: newFormat("table", false)},
`ID NAME CREATED UPDATED
1 passwords Less than a second ago Less than a second ago
2 id_rsa Less than a second ago Less than a second ago
`,
},
{
formatter.Context{Format: NewFormat("table {{.Name}}", true)},
formatter.Context{Format: newFormat("table {{.Name}}", true)},
`NAME
passwords
id_rsa
`,
},
{
formatter.Context{Format: NewFormat("{{.ID}}-{{.Name}}", false)},
formatter.Context{Format: newFormat("{{.ID}}-{{.Name}}", false)},
`1-passwords
2-id_rsa
`,
},
}
configs := []swarm.Config{
{
ID: "1",
Meta: swarm.Meta{CreatedAt: time.Now(), UpdatedAt: time.Now()},
Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "passwords"}},
},
{
ID: "2",
Meta: swarm.Meta{CreatedAt: time.Now(), UpdatedAt: time.Now()},
Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "id_rsa"}},
res := client.ConfigListResult{
Items: []swarm.Config{
{
ID: "1",
Meta: swarm.Meta{CreatedAt: time.Now(), UpdatedAt: time.Now()},
Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "passwords"}},
},
{
ID: "2",
Meta: swarm.Meta{CreatedAt: time.Now(), UpdatedAt: time.Now()},
Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "id_rsa"}},
},
},
}
for _, tc := range cases {
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out
if err := FormatWrite(tc.context, configs); err != nil {
if err := formatWrite(tc.context, res); err != nil {
assert.ErrorContains(t, err, tc.expected)
} else {
assert.Equal(t, out.String(), tc.expected)

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package config
@ -12,61 +12,61 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
// InspectOptions contains options for the docker config inspect command.
type InspectOptions struct {
Names []string
Format string
Pretty bool
// inspectOptions contains options for the docker config inspect command.
type inspectOptions struct {
names []string
format string
pretty bool
}
func newConfigInspectCommand(dockerCli command.Cli) *cobra.Command {
opts := InspectOptions{}
func newConfigInspectCommand(dockerCLI command.Cli) *cobra.Command {
opts := inspectOptions{}
cmd := &cobra.Command{
Use: "inspect [OPTIONS] CONFIG [CONFIG...]",
Short: "Display detailed information on one or more configs",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.Names = args
return RunConfigInspect(cmd.Context(), dockerCli, opts)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completeNames(dockerCli)(cmd, args, toComplete)
opts.names = args
return runInspect(cmd.Context(), dockerCLI, opts)
},
ValidArgsFunction: completeNames(dockerCLI),
DisableFlagsInUseLine: true,
}
cmd.Flags().StringVarP(&opts.Format, "format", "f", "", flagsHelper.InspectFormatHelp)
cmd.Flags().BoolVar(&opts.Pretty, "pretty", false, "Print the information in a human friendly format")
cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp)
cmd.Flags().BoolVar(&opts.pretty, "pretty", false, "Print the information in a human friendly format")
return cmd
}
// RunConfigInspect inspects the given Swarm config.
func RunConfigInspect(ctx context.Context, dockerCLI command.Cli, opts InspectOptions) error {
// runInspect inspects the given Swarm config.
func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error {
apiClient := dockerCLI.Client()
if opts.Pretty {
opts.Format = "pretty"
if opts.pretty {
opts.format = "pretty"
}
getRef := func(id string) (any, []byte, error) {
return apiClient.ConfigInspectWithRaw(ctx, id)
res, err := apiClient.ConfigInspect(ctx, id, client.ConfigInspectOptions{})
return res.Config, res.Raw, err
}
f := opts.Format
// check if the user is trying to apply a template to the pretty format, which
// is not supported
if strings.HasPrefix(f, "pretty") && f != "pretty" {
if strings.HasPrefix(opts.format, "pretty") && opts.format != "pretty" {
return errors.New("cannot supply extra formatting options to the pretty template")
}
configCtx := formatter.Context{
Output: dockerCLI.Out(),
Format: NewFormat(f, false),
Format: newFormat(opts.format, false),
}
if err := InspectFormatWrite(configCtx, opts.Names, getRef); err != nil {
if err := inspectFormatWrite(configCtx, opts.names, getRef); err != nil {
return cli.StatusError{StatusCode: 1, Status: err.Error()}
}
return nil

View File

@ -10,7 +10,7 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types/swarm"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
)
@ -19,7 +19,7 @@ func TestConfigInspectErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
configInspectFunc func(_ context.Context, configID string) (swarm.Config, []byte, error)
configInspectFunc func(_ context.Context, configID string, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error)
expectedError string
}{
{
@ -27,8 +27,8 @@ func TestConfigInspectErrors(t *testing.T) {
},
{
args: []string{"foo"},
configInspectFunc: func(_ context.Context, configID string) (swarm.Config, []byte, error) {
return swarm.Config{}, nil, errors.New("error while inspecting the config")
configInspectFunc: func(context.Context, string, client.ConfigInspectOptions) (client.ConfigInspectResult, error) {
return client.ConfigInspectResult{}, errors.New("error while inspecting the config")
},
expectedError: "error while inspecting the config",
},
@ -41,11 +41,13 @@ func TestConfigInspectErrors(t *testing.T) {
},
{
args: []string{"foo", "bar"},
configInspectFunc: func(_ context.Context, configID string) (swarm.Config, []byte, error) {
configInspectFunc: func(_ context.Context, configID string, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error) {
if configID == "foo" {
return *builders.Config(builders.ConfigName("foo")), nil, nil
return client.ConfigInspectResult{
Config: *builders.Config(builders.ConfigName("foo")),
}, nil
}
return swarm.Config{}, nil, errors.New("error while inspecting the config")
return client.ConfigInspectResult{}, errors.New("error while inspecting the config")
},
expectedError: "error while inspecting the config",
},
@ -70,25 +72,34 @@ func TestConfigInspectWithoutFormat(t *testing.T) {
testCases := []struct {
name string
args []string
configInspectFunc func(_ context.Context, configID string) (swarm.Config, []byte, error)
configInspectFunc func(_ context.Context, configID string, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error)
}{
{
name: "single-config",
args: []string{"foo"},
configInspectFunc: func(_ context.Context, name string) (swarm.Config, []byte, error) {
configInspectFunc: func(_ context.Context, name string, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error) {
if name != "foo" {
return swarm.Config{}, nil, fmt.Errorf("invalid name, expected %s, got %s", "foo", name)
return client.ConfigInspectResult{}, fmt.Errorf("invalid name, expected %s, got %s", "foo", name)
}
return *builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")), nil, nil
return client.ConfigInspectResult{
Config: *builders.Config(
builders.ConfigID("ID-foo"),
builders.ConfigName("foo"),
),
}, nil
},
},
{
name: "multiple-configs-with-labels",
args: []string{"foo", "bar"},
configInspectFunc: func(_ context.Context, name string) (swarm.Config, []byte, error) {
return *builders.Config(builders.ConfigID("ID-"+name), builders.ConfigName(name), builders.ConfigLabels(map[string]string{
"label1": "label-foo",
})), nil, nil
configInspectFunc: func(_ context.Context, name string, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error) {
return client.ConfigInspectResult{
Config: *builders.Config(
builders.ConfigID("ID-"+name),
builders.ConfigName(name),
builders.ConfigLabels(map[string]string{"label1": "label-foo"}),
),
}, nil
},
},
}
@ -102,16 +113,19 @@ func TestConfigInspectWithoutFormat(t *testing.T) {
}
func TestConfigInspectWithFormat(t *testing.T) {
configInspectFunc := func(_ context.Context, name string) (swarm.Config, []byte, error) {
return *builders.Config(builders.ConfigName("foo"), builders.ConfigLabels(map[string]string{
"label1": "label-foo",
})), nil, nil
configInspectFunc := func(_ context.Context, name string, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error) {
return client.ConfigInspectResult{
Config: *builders.Config(
builders.ConfigName("foo"),
builders.ConfigLabels(map[string]string{"label1": "label-foo"}),
),
}, nil
}
testCases := []struct {
name string
format string
args []string
configInspectFunc func(_ context.Context, name string) (swarm.Config, []byte, error)
configInspectFunc func(_ context.Context, name string, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error)
}{
{
name: "simple-template",
@ -141,21 +155,23 @@ func TestConfigInspectWithFormat(t *testing.T) {
func TestConfigInspectPretty(t *testing.T) {
testCases := []struct {
name string
configInspectFunc func(context.Context, string) (swarm.Config, []byte, error)
configInspectFunc func(context.Context, string, client.ConfigInspectOptions) (client.ConfigInspectResult, error)
}{
{
name: "simple",
configInspectFunc: func(_ context.Context, id string) (swarm.Config, []byte, error) {
return *builders.Config(
builders.ConfigLabels(map[string]string{
"lbl1": "value1",
}),
builders.ConfigID("configID"),
builders.ConfigName("configName"),
builders.ConfigCreatedAt(time.Time{}),
builders.ConfigUpdatedAt(time.Time{}),
builders.ConfigData([]byte("payload here")),
), []byte{}, nil
configInspectFunc: func(_ context.Context, id string, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error) {
return client.ConfigInspectResult{
Config: *builders.Config(
builders.ConfigLabels(map[string]string{
"lbl1": "value1",
}),
builders.ConfigID("configID"),
builders.ConfigName("configName"),
builders.ConfigCreatedAt(time.Time{}),
builders.ConfigUpdatedAt(time.Time{}),
builders.ConfigData([]byte("payload here")),
),
}, nil
},
},
}

View File

@ -6,24 +6,23 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/swarm"
"github.com/fvbommel/sortorder"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
// ListOptions contains options for the docker config ls command.
type ListOptions struct {
Quiet bool
Format string
Filter opts.FilterOpt
// listOptions contains options for the docker config ls command.
type listOptions struct {
quiet bool
format string
filter opts.FilterOpt
}
func newConfigListCommand(dockerCli command.Cli) *cobra.Command {
listOpts := ListOptions{Filter: opts.NewFilterOpt()}
func newConfigListCommand(dockerCLI command.Cli) *cobra.Command {
listOpts := listOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ls [OPTIONS]",
@ -31,44 +30,45 @@ func newConfigListCommand(dockerCli command.Cli) *cobra.Command {
Short: "List configs",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return RunConfigList(cmd.Context(), dockerCli, listOpts)
return runList(cmd.Context(), dockerCLI, listOpts)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
flags.BoolVarP(&listOpts.Quiet, "quiet", "q", false, "Only display IDs")
flags.StringVar(&listOpts.Format, "format", "", flagsHelper.FormatHelp)
flags.VarP(&listOpts.Filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVarP(&listOpts.quiet, "quiet", "q", false, "Only display IDs")
flags.StringVar(&listOpts.format, "format", "", flagsHelper.FormatHelp)
flags.VarP(&listOpts.filter, "filter", "f", "Filter output based on conditions provided")
return cmd
}
// RunConfigList lists Swarm configs.
func RunConfigList(ctx context.Context, dockerCLI command.Cli, options ListOptions) error {
// runList lists Swarm configs.
func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) error {
apiClient := dockerCLI.Client()
configs, err := apiClient.ConfigList(ctx, swarm.ConfigListOptions{Filters: options.Filter.Value()})
res, err := apiClient.ConfigList(ctx, client.ConfigListOptions{Filters: options.filter.Value()})
if err != nil {
return err
}
format := options.Format
format := options.format
if len(format) == 0 {
if len(dockerCLI.ConfigFile().ConfigFormat) > 0 && !options.Quiet {
if len(dockerCLI.ConfigFile().ConfigFormat) > 0 && !options.quiet {
format = dockerCLI.ConfigFile().ConfigFormat
} else {
format = formatter.TableFormatKey
}
}
sort.Slice(configs, func(i, j int) bool {
return sortorder.NaturalLess(configs[i].Spec.Name, configs[j].Spec.Name)
sort.Slice(res.Items, func(i, j int) bool {
return sortorder.NaturalLess(res.Items[i].Spec.Name, res.Items[j].Spec.Name)
})
configCtx := formatter.Context{
Output: dockerCLI.Out(),
Format: NewFormat(format, options.Quiet),
Format: newFormat(format, options.quiet),
}
return FormatWrite(configCtx, configs)
return formatWrite(configCtx, res)
}

View File

@ -10,16 +10,16 @@ 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/swarm"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
)
func TestConfigListErrors(t *testing.T) {
testCases := []struct {
args []string
configListFunc func(context.Context, swarm.ConfigListOptions) ([]swarm.Config, error)
configListFunc func(context.Context, client.ConfigListOptions) (client.ConfigListResult, error)
expectedError string
}{
{
@ -27,8 +27,8 @@ func TestConfigListErrors(t *testing.T) {
expectedError: "accepts no argument",
},
{
configListFunc: func(_ context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{}, errors.New("error listing configs")
configListFunc: func(_ context.Context, options client.ConfigListOptions) (client.ConfigListResult, error) {
return client.ConfigListResult{}, errors.New("error listing configs")
},
expectedError: "error listing configs",
},
@ -48,26 +48,28 @@ func TestConfigListErrors(t *testing.T) {
func TestConfigList(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(_ context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
*builders.Config(builders.ConfigID("ID-1-foo"),
builders.ConfigName("1-foo"),
builders.ConfigVersion(swarm.Version{Index: 10}),
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
*builders.Config(builders.ConfigID("ID-10-foo"),
builders.ConfigName("10-foo"),
builders.ConfigVersion(swarm.Version{Index: 11}),
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
*builders.Config(builders.ConfigID("ID-2-foo"),
builders.ConfigName("2-foo"),
builders.ConfigVersion(swarm.Version{Index: 11}),
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
configListFunc: func(_ context.Context, options client.ConfigListOptions) (client.ConfigListResult, error) {
return client.ConfigListResult{
Items: []swarm.Config{
*builders.Config(builders.ConfigID("ID-1-foo"),
builders.ConfigName("1-foo"),
builders.ConfigVersion(swarm.Version{Index: 10}),
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
*builders.Config(builders.ConfigID("ID-10-foo"),
builders.ConfigName("10-foo"),
builders.ConfigVersion(swarm.Version{Index: 11}),
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
*builders.Config(builders.ConfigID("ID-2-foo"),
builders.ConfigName("2-foo"),
builders.ConfigVersion(swarm.Version{Index: 11}),
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
},
}, nil
},
})
@ -78,12 +80,14 @@ func TestConfigList(t *testing.T) {
func TestConfigListWithQuietOption(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(_ context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
"label": "label-bar",
})),
configListFunc: func(_ context.Context, options client.ConfigListOptions) (client.ConfigListResult, error) {
return client.ConfigListResult{
Items: []swarm.Config{
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
"label": "label-bar",
})),
},
}, nil
},
})
@ -95,12 +99,14 @@ func TestConfigListWithQuietOption(t *testing.T) {
func TestConfigListWithConfigFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(_ context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
"label": "label-bar",
})),
configListFunc: func(_ context.Context, options client.ConfigListOptions) (client.ConfigListResult, error) {
return client.ConfigListResult{
Items: []swarm.Config{
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
"label": "label-bar",
})),
},
}, nil
},
})
@ -114,12 +120,14 @@ func TestConfigListWithConfigFormat(t *testing.T) {
func TestConfigListWithFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(_ context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
"label": "label-bar",
})),
configListFunc: func(_ context.Context, options client.ConfigListOptions) (client.ConfigListResult, error) {
return client.ConfigListResult{
Items: []swarm.Config{
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
"label": "label-bar",
})),
},
}, nil
},
})
@ -131,22 +139,24 @@ func TestConfigListWithFormat(t *testing.T) {
func TestConfigListWithFilter(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(_ context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
assert.Check(t, is.Equal("foo", options.Filters.Get("name")[0]))
assert.Check(t, is.Equal("lbl1=Label-bar", options.Filters.Get("label")[0]))
return []swarm.Config{
*builders.Config(builders.ConfigID("ID-foo"),
builders.ConfigName("foo"),
builders.ConfigVersion(swarm.Version{Index: 10}),
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
*builders.Config(builders.ConfigID("ID-bar"),
builders.ConfigName("bar"),
builders.ConfigVersion(swarm.Version{Index: 11}),
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
configListFunc: func(_ context.Context, options client.ConfigListOptions) (client.ConfigListResult, error) {
assert.Check(t, options.Filters["name"]["foo"])
assert.Check(t, options.Filters["label"]["lbl1=Label-bar"])
return client.ConfigListResult{
Items: []swarm.Config{
*builders.Config(builders.ConfigID("ID-foo"),
builders.ConfigName("foo"),
builders.ConfigVersion(swarm.Version{Index: 10}),
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
*builders.Config(builders.ConfigID("ID-bar"),
builders.ConfigName("bar"),
builders.ConfigVersion(swarm.Version{Index: 11}),
builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
},
}, nil
},
})

View File

@ -7,39 +7,31 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
// RemoveOptions contains options for the docker config rm command.
type RemoveOptions struct {
Names []string
}
func newConfigRemoveCommand(dockerCli command.Cli) *cobra.Command {
func newConfigRemoveCommand(dockerCLI command.Cli) *cobra.Command {
return &cobra.Command{
Use: "rm CONFIG [CONFIG...]",
Aliases: []string{"remove"},
Short: "Remove one or more configs",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts := RemoveOptions{
Names: args,
}
return RunConfigRemove(cmd.Context(), dockerCli, opts)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completeNames(dockerCli)(cmd, args, toComplete)
return runRemove(cmd.Context(), dockerCLI, args)
},
ValidArgsFunction: completeNames(dockerCLI),
DisableFlagsInUseLine: true,
}
}
// RunConfigRemove removes the given Swarm configs.
func RunConfigRemove(ctx context.Context, dockerCLI command.Cli, opts RemoveOptions) error {
// runRemove removes the given Swarm configs.
func runRemove(ctx context.Context, dockerCLI command.Cli, names []string) error {
apiClient := dockerCLI.Client()
var errs []error
for _, name := range opts.Names {
if err := apiClient.ConfigRemove(ctx, name); err != nil {
for _, name := range names {
if _, err := apiClient.ConfigRemove(ctx, name, client.ConfigRemoveOptions{}); err != nil {
errs = append(errs, err)
continue
}

View File

@ -1,12 +1,14 @@
package config
import (
"context"
"errors"
"io"
"strings"
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@ -14,7 +16,7 @@ import (
func TestConfigRemoveErrors(t *testing.T) {
testCases := []struct {
args []string
configRemoveFunc func(string) error
configRemoveFunc func(context.Context, string, client.ConfigRemoveOptions) (client.ConfigRemoveResult, error)
expectedError string
}{
{
@ -23,8 +25,8 @@ func TestConfigRemoveErrors(t *testing.T) {
},
{
args: []string{"foo"},
configRemoveFunc: func(name string) error {
return errors.New("error removing config")
configRemoveFunc: func(ctx context.Context, name string, options client.ConfigRemoveOptions) (client.ConfigRemoveResult, error) {
return client.ConfigRemoveResult{}, errors.New("error removing config")
},
expectedError: "error removing config",
},
@ -46,9 +48,9 @@ func TestConfigRemoveWithName(t *testing.T) {
names := []string{"foo", "bar"}
var removedConfigs []string
cli := test.NewFakeCli(&fakeClient{
configRemoveFunc: func(name string) error {
configRemoveFunc: func(_ context.Context, name string, _ client.ConfigRemoveOptions) (client.ConfigRemoveResult, error) {
removedConfigs = append(removedConfigs, name)
return nil
return client.ConfigRemoveResult{}, nil
},
})
cmd := newConfigRemoveCommand(cli)
@ -63,12 +65,12 @@ func TestConfigRemoveContinueAfterError(t *testing.T) {
var removedConfigs []string
cli := test.NewFakeCli(&fakeClient{
configRemoveFunc: func(name string) error {
configRemoveFunc: func(_ context.Context, name string, _ client.ConfigRemoveOptions) (client.ConfigRemoveResult, error) {
removedConfigs = append(removedConfigs, name)
if name == "foo" {
return errors.New("error removing config: " + name)
return client.ConfigRemoveResult{}, errors.New("error removing config: " + name)
}
return nil
return client.ConfigRemoveResult{}, nil
},
})

View File

@ -2,15 +2,15 @@ package container
import (
"context"
"errors"
"io"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/moby/sys/signal"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@ -23,25 +23,25 @@ type AttachOptions struct {
}
func inspectContainerAndCheckState(ctx context.Context, apiClient client.APIClient, args string) (*container.InspectResponse, error) {
c, err := apiClient.ContainerInspect(ctx, args)
c, err := apiClient.ContainerInspect(ctx, args, client.ContainerInspectOptions{})
if err != nil {
return nil, err
}
if !c.State.Running {
return nil, errors.New("You cannot attach to a stopped container, start it first")
if !c.Container.State.Running {
return nil, errors.New("cannot attach to a stopped container, start it first")
}
if c.State.Paused {
return nil, errors.New("You cannot attach to a paused container, unpause it first")
if c.Container.State.Paused {
return nil, errors.New("cannot attach to a paused container, unpause it first")
}
if c.State.Restarting {
return nil, errors.New("You cannot attach to a restarting container, wait until it is running")
if c.Container.State.Restarting {
return nil, errors.New("cannot attach to a restarting container, wait until it is running")
}
return &c, nil
return &c.Container, nil
}
// NewAttachCommand creates a new cobra.Command for `docker attach`
func NewAttachCommand(dockerCLI command.Cli) *cobra.Command {
// newAttachCommand creates a new cobra.Command for `docker attach`
func newAttachCommand(dockerCLI command.Cli) *cobra.Command {
var opts AttachOptions
cmd := &cobra.Command{
@ -58,6 +58,7 @@ func NewAttachCommand(dockerCLI command.Cli) *cobra.Command {
ValidArgsFunction: completion.ContainerNames(dockerCLI, false, func(ctr container.Summary) bool {
return ctr.State != container.StatePaused
}),
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -73,7 +74,7 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
// request channel to wait for client
waitCtx := context.WithoutCancel(ctx)
resultC, errC := apiClient.ContainerWait(waitCtx, containerID, "")
waitRes := apiClient.ContainerWait(waitCtx, containerID, client.ContainerWaitOptions{})
c, err := inspectContainerAndCheckState(ctx, apiClient, containerID)
if err != nil {
@ -89,7 +90,7 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
detachKeys = opts.DetachKeys
}
options := container.AttachOptions{
options := client.ContainerAttachOptions{
Stream: true,
Stdin: !opts.NoStdin && c.Config.OpenStdin,
Stdout: true,
@ -113,11 +114,11 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
defer signal.StopCatch(sigc)
}
resp, errAttach := apiClient.ContainerAttach(ctx, containerID, options)
if errAttach != nil {
return errAttach
res, err := apiClient.ContainerAttach(ctx, containerID, options)
if err != nil {
return err
}
defer resp.Close()
defer res.HijackedResponse.Close()
// If use docker attach command to attach to a stop container, it will return
// "You cannot attach to a stopped container" error, it's ok, but when
@ -141,7 +142,7 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
inputStream: in,
outputStream: dockerCLI.Out(),
errorStream: dockerCLI.Err(),
resp: resp,
resp: res.HijackedResponse,
tty: c.Config.Tty,
detachKeys: options.DetachKeys,
}
@ -151,19 +152,19 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
return err
}
return getExitStatus(errC, resultC)
return getExitStatus(waitRes)
}
func getExitStatus(errC <-chan error, resultC <-chan container.WaitResponse) error {
func getExitStatus(waitRes client.ContainerWaitResult) error {
select {
case result := <-resultC:
case result := <-waitRes.Result:
if result.Error != nil {
return errors.New(result.Error.Message)
}
if result.StatusCode != 0 {
return cli.StatusError{StatusCode: int(result.StatusCode)}
}
case err := <-errC:
case err := <-waitRes.Error:
return err
}
@ -176,7 +177,7 @@ func resizeTTY(ctx context.Context, dockerCli command.Cli, containerID string) {
// terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
// resize it, then go back to normal. Without this, every attach after the first will
// require the user to manually resize or hit enter.
resizeTtyTo(ctx, dockerCli.Client(), containerID, height+1, width+1, false)
resizeTTYTo(ctx, dockerCli.Client(), containerID, height+1, width+1, false)
// After the above resizing occurs, the call to MonitorTtySize below will handle resetting back
// to the actual size.

View File

@ -7,7 +7,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
)
@ -16,23 +17,23 @@ func TestNewAttachCommandErrors(t *testing.T) {
name string
args []string
expectedError string
containerInspectFunc func(img string) (container.InspectResponse, error)
containerInspectFunc func(img string) (client.ContainerInspectResult, error)
}{
{
name: "client-error",
args: []string{"5cb5bb5e4a3b"},
expectedError: "something went wrong",
containerInspectFunc: func(containerID string) (container.InspectResponse, error) {
return container.InspectResponse{}, errors.New("something went wrong")
containerInspectFunc: func(containerID string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{}, errors.New("something went wrong")
},
},
{
name: "client-stopped",
args: []string{"5cb5bb5e4a3b"},
expectedError: "You cannot attach to a stopped container",
containerInspectFunc: func(containerID string) (container.InspectResponse, error) {
return container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
expectedError: "cannot attach to a stopped container",
containerInspectFunc: func(containerID string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{
Container: container.InspectResponse{
State: &container.State{
Running: false,
},
@ -43,10 +44,10 @@ func TestNewAttachCommandErrors(t *testing.T) {
{
name: "client-paused",
args: []string{"5cb5bb5e4a3b"},
expectedError: "You cannot attach to a paused container",
containerInspectFunc: func(containerID string) (container.InspectResponse, error) {
return container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
expectedError: "cannot attach to a paused container",
containerInspectFunc: func(containerID string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{
Container: container.InspectResponse{
State: &container.State{
Running: true,
Paused: true,
@ -58,10 +59,10 @@ func TestNewAttachCommandErrors(t *testing.T) {
{
name: "client-restarting",
args: []string{"5cb5bb5e4a3b"},
expectedError: "You cannot attach to a restarting container",
containerInspectFunc: func(containerID string) (container.InspectResponse, error) {
return container.InspectResponse{
ContainerJSONBase: &container.ContainerJSONBase{
expectedError: "cannot attach to a restarting container",
containerInspectFunc: func(containerID string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{
Container: container.InspectResponse{
State: &container.State{
Running: true,
Paused: false,
@ -74,7 +75,7 @@ func TestNewAttachCommandErrors(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc}))
cmd := newAttachCommand(test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc}))
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs(tc.args)
@ -124,7 +125,10 @@ func TestGetExitStatus(t *testing.T) {
resultC <- *testcase.result
}
err := getExitStatus(errC, resultC)
err := getExitStatus(client.ContainerWaitResult{
Result: resultC,
Error: errC,
})
if testcase.expectedError == nil {
assert.NilError(t, err)

View File

@ -3,232 +3,232 @@ package container
import (
"context"
"io"
"net/http"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/client"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/moby/moby/client"
)
func mockContainerExportResult(content string) client.ContainerExportResult {
return io.NopCloser(strings.NewReader(content))
}
func mockContainerLogsResult(content string) client.ContainerLogsResult {
return io.NopCloser(strings.NewReader(content))
}
type fakeStreamResult struct {
io.ReadCloser
client.ImagePushResponse // same interface as [client.ImagePushResponse]
}
func (e fakeStreamResult) Read(p []byte) (int, error) { return e.ReadCloser.Read(p) }
func (e fakeStreamResult) Close() error { return e.ReadCloser.Close() }
type fakeClient struct {
client.Client
inspectFunc func(string) (container.InspectResponse, error)
execInspectFunc func(execID string) (container.ExecInspect, error)
execCreateFunc func(containerID string, options container.ExecOptions) (container.ExecCreateResponse, error)
createContainerFunc func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string) (container.CreateResponse, error)
containerStartFunc func(containerID string, options container.StartOptions) error
imageCreateFunc func(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error)
infoFunc func() (system.Info, error)
containerStatPathFunc func(containerID, path string) (container.PathStat, error)
containerCopyFromFunc func(containerID, srcPath string) (io.ReadCloser, container.PathStat, error)
logFunc func(string, container.LogsOptions) (io.ReadCloser, error)
waitFunc func(string) (<-chan container.WaitResponse, <-chan error)
containerListFunc func(container.ListOptions) ([]container.Summary, error)
containerExportFunc func(string) (io.ReadCloser, error)
containerExecResizeFunc func(id string, options container.ResizeOptions) error
containerRemoveFunc func(ctx context.Context, containerID string, options container.RemoveOptions) error
containerRestartFunc func(ctx context.Context, containerID string, options container.StopOptions) error
containerStopFunc func(ctx context.Context, containerID string, options container.StopOptions) error
containerKillFunc func(ctx context.Context, containerID, signal string) error
containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error)
containerAttachFunc func(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error)
containerDiffFunc func(ctx context.Context, containerID string) ([]container.FilesystemChange, error)
inspectFunc func(string) (client.ContainerInspectResult, error)
execInspectFunc func(execID string) (client.ExecInspectResult, error)
execCreateFunc func(containerID string, options client.ExecCreateOptions) (client.ExecCreateResult, error)
createContainerFunc func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error)
containerStartFunc func(containerID string, options client.ContainerStartOptions) (client.ContainerStartResult, error)
imagePullFunc func(ctx context.Context, parentReference string, options client.ImagePullOptions) (client.ImagePullResponse, error)
infoFunc func() (client.SystemInfoResult, error)
containerStatPathFunc func(containerID, path string) (client.ContainerStatPathResult, error)
containerCopyFromFunc func(containerID, srcPath string) (client.CopyFromContainerResult, error)
logFunc func(string, client.ContainerLogsOptions) (client.ContainerLogsResult, error)
waitFunc func(string) client.ContainerWaitResult
containerListFunc func(client.ContainerListOptions) (client.ContainerListResult, error)
containerExportFunc func(string) (client.ContainerExportResult, error)
containerExecResizeFunc func(id string, options client.ExecResizeOptions) (client.ExecResizeResult, error)
containerRemoveFunc func(ctx context.Context, containerID string, options client.ContainerRemoveOptions) (client.ContainerRemoveResult, error)
containerRestartFunc func(ctx context.Context, containerID string, options client.ContainerRestartOptions) (client.ContainerRestartResult, error)
containerStopFunc func(ctx context.Context, containerID string, options client.ContainerStopOptions) (client.ContainerStopResult, error)
containerKillFunc func(ctx context.Context, containerID string, options client.ContainerKillOptions) (client.ContainerKillResult, error)
containerPruneFunc func(ctx context.Context, options client.ContainerPruneOptions) (client.ContainerPruneResult, error)
containerAttachFunc func(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.ContainerAttachResult, error)
containerDiffFunc func(ctx context.Context, containerID string) (client.ContainerDiffResult, error)
containerRenameFunc func(ctx context.Context, oldName, newName string) error
containerCommitFunc func(ctx context.Context, container string, options container.CommitOptions) (container.CommitResponse, error)
containerPauseFunc func(ctx context.Context, container string) error
containerCommitFunc func(ctx context.Context, container string, options client.ContainerCommitOptions) (client.ContainerCommitResult, error)
containerPauseFunc func(ctx context.Context, container string, options client.ContainerPauseOptions) (client.ContainerPauseResult, error)
Version string
}
func (f *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]container.Summary, error) {
func (f *fakeClient) ContainerList(_ context.Context, options client.ContainerListOptions) (client.ContainerListResult, error) {
if f.containerListFunc != nil {
return f.containerListFunc(options)
}
return []container.Summary{}, nil
return client.ContainerListResult{}, nil
}
func (f *fakeClient) ContainerInspect(_ context.Context, containerID string) (container.InspectResponse, error) {
func (f *fakeClient) ContainerInspect(_ context.Context, containerID string, _ client.ContainerInspectOptions) (client.ContainerInspectResult, error) {
if f.inspectFunc != nil {
return f.inspectFunc(containerID)
}
return container.InspectResponse{}, nil
return client.ContainerInspectResult{}, nil
}
func (f *fakeClient) ContainerExecCreate(_ context.Context, containerID string, config container.ExecOptions) (container.ExecCreateResponse, error) {
func (f *fakeClient) ExecCreate(_ context.Context, containerID string, config client.ExecCreateOptions) (client.ExecCreateResult, error) {
if f.execCreateFunc != nil {
return f.execCreateFunc(containerID, config)
}
return container.ExecCreateResponse{}, nil
return client.ExecCreateResult{}, nil
}
func (f *fakeClient) ContainerExecInspect(_ context.Context, execID string) (container.ExecInspect, error) {
func (f *fakeClient) ExecInspect(_ context.Context, execID string, _ client.ExecInspectOptions) (client.ExecInspectResult, error) {
if f.execInspectFunc != nil {
return f.execInspectFunc(execID)
}
return container.ExecInspect{}, nil
return client.ExecInspectResult{}, nil
}
func (*fakeClient) ContainerExecStart(context.Context, string, container.ExecStartOptions) error {
return nil
func (*fakeClient) ExecStart(context.Context, string, client.ExecStartOptions) (client.ExecStartResult, error) {
return client.ExecStartResult{}, nil
}
func (f *fakeClient) ContainerCreate(
_ context.Context,
config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string,
) (container.CreateResponse, error) {
func (f *fakeClient) ContainerCreate(_ context.Context, options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
if f.createContainerFunc != nil {
return f.createContainerFunc(config, hostConfig, networkingConfig, platform, containerName)
return f.createContainerFunc(options)
}
return container.CreateResponse{}, nil
return client.ContainerCreateResult{}, nil
}
func (f *fakeClient) ContainerRemove(ctx context.Context, containerID string, options container.RemoveOptions) error {
func (f *fakeClient) ContainerRemove(ctx context.Context, containerID string, options client.ContainerRemoveOptions) (client.ContainerRemoveResult, error) {
if f.containerRemoveFunc != nil {
return f.containerRemoveFunc(ctx, containerID, options)
}
return nil
return client.ContainerRemoveResult{}, nil
}
func (f *fakeClient) ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
if f.imageCreateFunc != nil {
return f.imageCreateFunc(ctx, parentReference, options)
func (f *fakeClient) ImagePull(ctx context.Context, parentReference string, options client.ImagePullOptions) (client.ImagePullResponse, error) {
if f.imagePullFunc != nil {
return f.imagePullFunc(ctx, parentReference, options)
}
return nil, nil
return fakeStreamResult{}, nil
}
func (f *fakeClient) Info(_ context.Context) (system.Info, error) {
func (f *fakeClient) Info(context.Context, client.InfoOptions) (client.SystemInfoResult, error) {
if f.infoFunc != nil {
return f.infoFunc()
}
return system.Info{}, nil
return client.SystemInfoResult{}, nil
}
func (f *fakeClient) ContainerStatPath(_ context.Context, containerID, path string) (container.PathStat, error) {
func (f *fakeClient) ContainerStatPath(_ context.Context, containerID string, options client.ContainerStatPathOptions) (client.ContainerStatPathResult, error) {
if f.containerStatPathFunc != nil {
return f.containerStatPathFunc(containerID, path)
return f.containerStatPathFunc(containerID, options.Path)
}
return container.PathStat{}, nil
return client.ContainerStatPathResult{}, nil
}
func (f *fakeClient) CopyFromContainer(_ context.Context, containerID, srcPath string) (io.ReadCloser, container.PathStat, error) {
func (f *fakeClient) CopyFromContainer(_ context.Context, containerID string, options client.CopyFromContainerOptions) (client.CopyFromContainerResult, error) {
if f.containerCopyFromFunc != nil {
return f.containerCopyFromFunc(containerID, srcPath)
return f.containerCopyFromFunc(containerID, options.SourcePath)
}
return nil, container.PathStat{}, nil
return client.CopyFromContainerResult{}, nil
}
func (f *fakeClient) ContainerLogs(_ context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
func (f *fakeClient) ContainerLogs(_ context.Context, containerID string, options client.ContainerLogsOptions) (client.ContainerLogsResult, error) {
if f.logFunc != nil {
return f.logFunc(containerID, options)
}
return nil, nil
return http.NoBody, nil
}
func (f *fakeClient) ClientVersion() string {
return f.Version
}
func (f *fakeClient) ContainerWait(_ context.Context, containerID string, _ container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
func (f *fakeClient) ContainerWait(_ context.Context, containerID string, _ client.ContainerWaitOptions) client.ContainerWaitResult {
if f.waitFunc != nil {
return f.waitFunc(containerID)
}
return nil, nil
return client.ContainerWaitResult{}
}
func (f *fakeClient) ContainerStart(_ context.Context, containerID string, options container.StartOptions) error {
func (f *fakeClient) ContainerStart(_ context.Context, containerID string, options client.ContainerStartOptions) (client.ContainerStartResult, error) {
if f.containerStartFunc != nil {
return f.containerStartFunc(containerID, options)
}
return nil
return client.ContainerStartResult{}, nil
}
func (f *fakeClient) ContainerExport(_ context.Context, containerID string) (io.ReadCloser, error) {
func (f *fakeClient) ContainerExport(_ context.Context, containerID string, _ client.ContainerExportOptions) (client.ContainerExportResult, error) {
if f.containerExportFunc != nil {
return f.containerExportFunc(containerID)
}
return nil, nil
return http.NoBody, nil
}
func (f *fakeClient) ContainerExecResize(_ context.Context, id string, options container.ResizeOptions) error {
func (f *fakeClient) ExecResize(_ context.Context, id string, options client.ExecResizeOptions) (client.ExecResizeResult, error) {
if f.containerExecResizeFunc != nil {
return f.containerExecResizeFunc(id, options)
}
return nil
return client.ExecResizeResult{}, nil
}
func (f *fakeClient) ContainerKill(ctx context.Context, containerID, signal string) error {
func (f *fakeClient) ContainerKill(ctx context.Context, containerID string, options client.ContainerKillOptions) (client.ContainerKillResult, error) {
if f.containerKillFunc != nil {
return f.containerKillFunc(ctx, containerID, signal)
return f.containerKillFunc(ctx, containerID, options)
}
return nil
return client.ContainerKillResult{}, nil
}
func (f *fakeClient) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
func (f *fakeClient) ContainerPrune(ctx context.Context, options client.ContainerPruneOptions) (client.ContainerPruneResult, error) {
if f.containerPruneFunc != nil {
return f.containerPruneFunc(ctx, pruneFilters)
return f.containerPruneFunc(ctx, options)
}
return container.PruneReport{}, nil
return client.ContainerPruneResult{}, nil
}
func (f *fakeClient) ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error {
func (f *fakeClient) ContainerRestart(ctx context.Context, containerID string, options client.ContainerRestartOptions) (client.ContainerRestartResult, error) {
if f.containerRestartFunc != nil {
return f.containerRestartFunc(ctx, containerID, options)
}
return nil
return client.ContainerRestartResult{}, nil
}
func (f *fakeClient) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error {
func (f *fakeClient) ContainerStop(ctx context.Context, containerID string, options client.ContainerStopOptions) (client.ContainerStopResult, error) {
if f.containerStopFunc != nil {
return f.containerStopFunc(ctx, containerID, options)
}
return nil
return client.ContainerStopResult{}, nil
}
func (f *fakeClient) ContainerAttach(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
func (f *fakeClient) ContainerAttach(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.ContainerAttachResult, error) {
if f.containerAttachFunc != nil {
return f.containerAttachFunc(ctx, containerID, options)
}
return types.HijackedResponse{}, nil
return client.ContainerAttachResult{}, nil
}
func (f *fakeClient) ContainerDiff(ctx context.Context, containerID string) ([]container.FilesystemChange, error) {
func (f *fakeClient) ContainerDiff(ctx context.Context, containerID string, _ client.ContainerDiffOptions) (client.ContainerDiffResult, error) {
if f.containerDiffFunc != nil {
return f.containerDiffFunc(ctx, containerID)
}
return []container.FilesystemChange{}, nil
return client.ContainerDiffResult{}, nil
}
func (f *fakeClient) ContainerRename(ctx context.Context, oldName, newName string) error {
func (f *fakeClient) ContainerRename(ctx context.Context, oldName string, options client.ContainerRenameOptions) (client.ContainerRenameResult, error) {
if f.containerRenameFunc != nil {
return f.containerRenameFunc(ctx, oldName, newName)
return client.ContainerRenameResult{}, f.containerRenameFunc(ctx, oldName, options.NewName)
}
return nil
return client.ContainerRenameResult{}, nil
}
func (f *fakeClient) ContainerCommit(ctx context.Context, containerID string, options container.CommitOptions) (container.CommitResponse, error) {
func (f *fakeClient) ContainerCommit(ctx context.Context, containerID string, options client.ContainerCommitOptions) (client.ContainerCommitResult, error) {
if f.containerCommitFunc != nil {
return f.containerCommitFunc(ctx, containerID, options)
}
return container.CommitResponse{}, nil
return client.ContainerCommitResult{}, nil
}
func (f *fakeClient) ContainerPause(ctx context.Context, containerID string) error {
func (f *fakeClient) ContainerPause(ctx context.Context, containerID string, options client.ContainerPauseOptions) (client.ContainerPauseResult, error) {
if f.containerPauseFunc != nil {
return f.containerPauseFunc(ctx, containerID)
return f.containerPauseFunc(ctx, containerID, options)
}
return nil
return client.ContainerPauseResult{}, nil
}

View File

@ -3,43 +3,73 @@ package container
import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/internal/commands"
"github.com/spf13/cobra"
)
// NewContainerCommand returns a cobra command for `container` subcommands
func NewContainerCommand(dockerCli command.Cli) *cobra.Command {
func init() {
commands.Register(newRunCommand)
commands.Register(newExecCommand)
commands.Register(newPsCommand)
commands.Register(newContainerCommand)
commands.RegisterLegacy(newAttachCommand)
commands.RegisterLegacy(newCommitCommand)
commands.RegisterLegacy(newCopyCommand)
commands.RegisterLegacy(newCreateCommand)
commands.RegisterLegacy(newDiffCommand)
commands.RegisterLegacy(newExportCommand)
commands.RegisterLegacy(newKillCommand)
commands.RegisterLegacy(newLogsCommand)
commands.RegisterLegacy(newPauseCommand)
commands.RegisterLegacy(newPortCommand)
commands.RegisterLegacy(newRenameCommand)
commands.RegisterLegacy(newRestartCommand)
commands.RegisterLegacy(newRmCommand)
commands.RegisterLegacy(newStartCommand)
commands.RegisterLegacy(newStatsCommand)
commands.RegisterLegacy(newStopCommand)
commands.RegisterLegacy(newTopCommand)
commands.RegisterLegacy(newUnpauseCommand)
commands.RegisterLegacy(newUpdateCommand)
commands.RegisterLegacy(newWaitCommand)
}
// newContainerCommand returns a cobra command for `container` subcommands
func newContainerCommand(dockerCLI command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "container",
Short: "Manage containers",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
RunE: command.ShowHelp(dockerCLI.Err()),
DisableFlagsInUseLine: true,
}
cmd.AddCommand(
NewAttachCommand(dockerCli),
NewCommitCommand(dockerCli),
NewCopyCommand(dockerCli),
NewCreateCommand(dockerCli),
NewDiffCommand(dockerCli),
NewExecCommand(dockerCli),
NewExportCommand(dockerCli),
NewKillCommand(dockerCli),
NewLogsCommand(dockerCli),
NewPauseCommand(dockerCli),
NewPortCommand(dockerCli),
NewRenameCommand(dockerCli),
NewRestartCommand(dockerCli),
NewRmCommand(dockerCli),
NewRunCommand(dockerCli),
NewStartCommand(dockerCli),
NewStatsCommand(dockerCli),
NewStopCommand(dockerCli),
NewTopCommand(dockerCli),
NewUnpauseCommand(dockerCli),
NewUpdateCommand(dockerCli),
NewWaitCommand(dockerCli),
newListCommand(dockerCli),
newInspectCommand(dockerCli),
NewPruneCommand(dockerCli),
newAttachCommand(dockerCLI),
newCommitCommand(dockerCLI),
newCopyCommand(dockerCLI),
newCreateCommand(dockerCLI),
newDiffCommand(dockerCLI),
newExecCommand(dockerCLI),
newExportCommand(dockerCLI),
newKillCommand(dockerCLI),
newLogsCommand(dockerCLI),
newPauseCommand(dockerCLI),
newPortCommand(dockerCLI),
newRenameCommand(dockerCLI),
newRestartCommand(dockerCLI),
newRemoveCommand(dockerCLI),
newRunCommand(dockerCLI),
newStartCommand(dockerCLI),
newStatsCommand(dockerCLI),
newStopCommand(dockerCLI),
newTopCommand(dockerCLI),
newUnpauseCommand(dockerCLI),
newUpdateCommand(dockerCLI),
newWaitCommand(dockerCLI),
newListCommand(dockerCLI),
newInspectCommand(dockerCLI),
newPruneCommand(dockerCLI),
)
return cmd
}

View File

@ -2,13 +2,14 @@ package container
import (
"context"
"errors"
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -17,13 +18,14 @@ type commitOptions struct {
reference string
pause bool
noPause bool
comment string
author string
changes opts.ListOpts
}
// NewCommitCommand creates a new cobra.Command for `docker commit`
func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
// newCommitCommand creates a new cobra.Command for `docker commit`
func newCommitCommand(dockerCLI command.Cli) *cobra.Command {
var options commitOptions
cmd := &cobra.Command{
@ -35,18 +37,29 @@ func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
if len(args) > 1 {
options.reference = args[1]
}
return runCommit(cmd.Context(), dockerCli, &options)
if cmd.Flag("pause").Changed {
if cmd.Flag("no-pause").Changed {
return errors.New("conflicting options: --no-pause and --pause cannot be used together")
}
options.noPause = !options.pause
}
return runCommit(cmd.Context(), dockerCLI, &options)
},
Annotations: map[string]string{
"aliases": "docker container commit, docker commit",
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
flags.SetInterspersed(false)
flags.BoolVarP(&options.pause, "pause", "p", true, "Pause container during commit")
// TODO(thaJeztah): Deprecated: the --pause flag was deprecated in v29 and can be removed in v30.
flags.BoolVarP(&options.pause, "pause", "p", true, "Pause container during commit (deprecated: use --no-pause instead)")
_ = flags.MarkDeprecated("pause", "and enabled by default. Use --no-pause to disable pausing during commit.")
flags.BoolVar(&options.noPause, "no-pause", false, "Disable pausing container during commit")
flags.StringVarP(&options.comment, "message", "m", "", "Commit message")
flags.StringVarP(&options.author, "author", "a", "", `Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")`)
@ -57,17 +70,17 @@ func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
}
func runCommit(ctx context.Context, dockerCli command.Cli, options *commitOptions) error {
response, err := dockerCli.Client().ContainerCommit(ctx, options.container, container.CommitOptions{
response, err := dockerCli.Client().ContainerCommit(ctx, options.container, client.ContainerCommitOptions{
Reference: options.reference,
Comment: options.comment,
Author: options.author,
Changes: options.changes.GetSlice(),
Pause: options.pause,
NoPause: options.noPause,
})
if err != nil {
return err
}
fmt.Fprintln(dockerCli.Out(), response.ID)
_, _ = fmt.Fprintln(dockerCli.Out(), response.ID)
return nil
}

View File

@ -7,36 +7,32 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestRunCommit(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerCommitFunc: func(
ctx context.Context,
ctr string,
options container.CommitOptions,
) (container.CommitResponse, error) {
containerCommitFunc: func(ctx context.Context, ctr string, options client.ContainerCommitOptions) (client.ContainerCommitResult, error) {
assert.Check(t, is.Equal(options.Author, "Author Name <author@name.com>"))
assert.Check(t, is.DeepEqual(options.Changes, []string{"EXPOSE 80"}))
assert.Check(t, is.Equal(options.Comment, "commit message"))
assert.Check(t, is.Equal(options.Pause, false))
assert.Check(t, is.Equal(options.NoPause, true))
assert.Check(t, is.Equal(ctr, "container-id"))
return container.CommitResponse{ID: "image-id"}, nil
return client.ContainerCommitResult{ID: "image-id"}, nil
},
})
cmd := NewCommitCommand(cli)
cmd := newCommitCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs(
[]string{
"--author", "Author Name <author@name.com>",
"--change", "EXPOSE 80",
"--message", "commit message",
"--pause=false",
"--no-pause",
"container-id",
},
)
@ -51,16 +47,12 @@ func TestRunCommitClientError(t *testing.T) {
clientError := errors.New("client error")
cli := test.NewFakeCli(&fakeClient{
containerCommitFunc: func(
ctx context.Context,
ctr string,
options container.CommitOptions,
) (container.CommitResponse, error) {
return container.CommitResponse{}, clientError
containerCommitFunc: func(ctx context.Context, ctr string, options client.ContainerCommitOptions) (client.ContainerCommitResult, error) {
return client.ContainerCommitResult{}, clientError
},
})
cmd := NewCommitCommand(cli)
cmd := newCommitCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs([]string{"container-id"})

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package container
@ -8,7 +8,8 @@ import (
"sync"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/moby/sys/capability"
"github.com/moby/sys/signal"
"github.com/spf13/cobra"
@ -122,15 +123,15 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider)
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames)
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames)
_ = cmd.RegisterFlagCompletionFunc("cgroupns", completeCgroupns())
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames())
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames())
_ = cmd.RegisterFlagCompletionFunc("ipc", completeIpc(dockerCLI))
_ = cmd.RegisterFlagCompletionFunc("link", completeLink(dockerCLI))
_ = cmd.RegisterFlagCompletionFunc("log-driver", completeLogDriver(dockerCLI))
_ = cmd.RegisterFlagCompletionFunc("log-opt", completeLogOpt)
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCLI))
_ = cmd.RegisterFlagCompletionFunc("pid", completePid(dockerCLI))
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms())
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
_ = cmd.RegisterFlagCompletionFunc("security-opt", completeSecurityOpt)
@ -186,11 +187,11 @@ func completeLink(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
// of the build-in log drivers.
func completeLogDriver(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
info, err := dockerCLI.Client().Info(cmd.Context())
res, err := dockerCLI.Client().Info(cmd.Context(), client.InfoOptions{})
if err != nil {
return builtInLogDrivers(), cobra.ShellCompDirectiveNoFileComp
}
drivers := info.Plugins.Log
drivers := res.Info.Plugins.Log
return drivers, cobra.ShellCompDirectiveNoFileComp
}
}
@ -279,12 +280,12 @@ func completeUlimit(_ *cobra.Command, _ []string, _ string) ([]string, cobra.She
// completeVolumeDriver contacts the API to get the built-in and installed volume drivers.
func completeVolumeDriver(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
return func(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
info, err := dockerCLI.Client().Info(cmd.Context())
res, err := dockerCLI.Client().Info(cmd.Context(), client.InfoOptions{})
if err != nil {
// fallback: the built-in drivers
return []string{"local"}, cobra.ShellCompDirectiveNoFileComp
}
drivers := info.Plugins.Volume
drivers := res.Info.Plugins.Volume
return drivers, cobra.ShellCompDirectiveNoFileComp
}
}

View File

@ -6,7 +6,8 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/moby/sys/signal"
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
@ -26,7 +27,7 @@ func TestCompleteLinuxCapabilityNames(t *testing.T) {
func TestCompletePid(t *testing.T) {
tests := []struct {
containerListFunc func(container.ListOptions) ([]container.Summary, error)
containerListFunc func(client.ContainerListOptions) (client.ContainerListResult, error)
toComplete string
expectedCompletions []string
expectedDirective cobra.ShellCompDirective
@ -42,10 +43,12 @@ func TestCompletePid(t *testing.T) {
expectedDirective: cobra.ShellCompDirectiveNoSpace,
},
{
containerListFunc: func(container.ListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1"),
*builders.Container("c2"),
containerListFunc: func(client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1"),
*builders.Container("c2"),
},
}, nil
},
toComplete: "container:",
@ -59,7 +62,7 @@ func TestCompletePid(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: tc.containerListFunc,
})
completions, directive := completePid(cli)(NewRunCommand(cli), nil, tc.toComplete)
completions, directive := completePid(cli)(newRunCommand(cli), nil, tc.toComplete)
assert.Check(t, is.DeepEqual(completions, tc.expectedCompletions))
assert.Check(t, is.Equal(directive, tc.expectedDirective))
})

View File

@ -3,6 +3,7 @@ package container
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
@ -15,11 +16,10 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/api/types/container"
units "github.com/docker/go-units"
"github.com/docker/go-units"
"github.com/moby/go-archive"
"github.com/moby/moby/client"
"github.com/morikuni/aec"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -121,8 +121,8 @@ func copyProgress(ctx context.Context, dst io.Writer, header string, total *int6
return restore, done
}
// NewCopyCommand creates a new `docker cp` command
func NewCopyCommand(dockerCli command.Cli) *cobra.Command {
// newCopyCommand creates a new `docker cp` command
func newCopyCommand(dockerCLI command.Cli) *cobra.Command {
var opts copyOptions
cmd := &cobra.Command{
@ -147,13 +147,14 @@ container source to stdout.`,
opts.destination = args[1]
if !cmd.Flag("quiet").Changed {
// User did not specify "quiet" flag; suppress output if no terminal is attached
opts.quiet = !dockerCli.Out().IsTerminal()
opts.quiet = !dockerCLI.Out().IsTerminal()
}
return runCopy(cmd.Context(), dockerCli, opts)
return runCopy(cmd.Context(), dockerCLI, opts)
},
Annotations: map[string]string{
"aliases": "docker container cp, docker cp",
},
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -229,11 +230,13 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
// if client requests to follow symbol link, then must decide target file to be copied
var rebaseName string
if copyConfig.followLink {
srcStat, err := apiClient.ContainerStatPath(ctx, copyConfig.container, srcPath)
src, err := apiClient.ContainerStatPath(ctx, copyConfig.container, client.ContainerStatPathOptions{
Path: srcPath,
})
// If the destination is a symbolic link, we should follow it.
if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
linkTarget := srcStat.LinkTarget
if err == nil && src.Stat.Mode&os.ModeSymlink != 0 {
linkTarget := src.Stat.LinkTarget
if !isAbs(linkTarget) {
// Join with the parent directory.
srcParent, _ := archive.SplitPathDirEntry(srcPath)
@ -248,11 +251,14 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
defer cancel()
content, stat, err := apiClient.CopyFromContainer(ctx, copyConfig.container, srcPath)
cpRes, err := apiClient.CopyFromContainer(ctx, copyConfig.container, client.CopyFromContainerOptions{
SourcePath: srcPath,
})
if err != nil {
return err
}
defer content.Close()
content := cpRes.Content
defer func() { _ = content.Close() }()
if dstPath == "-" {
_, err = io.Copy(dockerCLI.Out(), content)
@ -262,7 +268,7 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
srcInfo := archive.CopyInfo{
Path: srcPath,
Exists: true,
IsDir: stat.Mode.IsDir(),
IsDir: cpRes.Stat.Mode.IsDir(),
RebaseName: rebaseName,
}
@ -298,50 +304,50 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
// about both the source and destination. The API is a simple tar
// archive/extract API but we can use the stat info header about the
// destination to be more informed about exactly what the destination is.
func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpConfig) (err error) {
func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpConfig) error {
srcPath := copyConfig.sourcePath
dstPath := copyConfig.destPath
if srcPath != "-" {
// Get an absolute source path.
srcPath, err = resolveLocalPath(srcPath)
p, err := resolveLocalPath(srcPath)
if err != nil {
return err
}
srcPath = p
}
apiClient := dockerCLI.Client()
// Prepare destination copy info by stat-ing the container path.
dstInfo := archive.CopyInfo{Path: dstPath}
dstStat, err := apiClient.ContainerStatPath(ctx, copyConfig.container, dstPath)
if dst, err := apiClient.ContainerStatPath(ctx, copyConfig.container, client.ContainerStatPathOptions{Path: dstPath}); err == nil {
// If the destination is a symbolic link, we should evaluate it.
if dst.Stat.Mode&os.ModeSymlink != 0 {
linkTarget := dst.Stat.LinkTarget
if !isAbs(linkTarget) {
// Join with the parent directory.
dstParent, _ := archive.SplitPathDirEntry(dstPath)
linkTarget = filepath.Join(dstParent, linkTarget)
}
// If the destination is a symbolic link, we should evaluate it.
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
linkTarget := dstStat.LinkTarget
if !isAbs(linkTarget) {
// Join with the parent directory.
dstParent, _ := archive.SplitPathDirEntry(dstPath)
linkTarget = filepath.Join(dstParent, linkTarget)
dstInfo.Path = linkTarget
dst, err = apiClient.ContainerStatPath(ctx, copyConfig.container, client.ContainerStatPathOptions{Path: linkTarget})
}
// Validate the destination path
if err == nil {
if err := command.ValidateOutputPathFileMode(dst.Stat.Mode); err != nil {
return fmt.Errorf(`destination "%s:%s" must be a directory or a regular file: %w`, copyConfig.container, dstPath, err)
}
dstInfo.Exists, dstInfo.IsDir = true, dst.Stat.Mode.IsDir()
}
dstInfo.Path = linkTarget
dstStat, err = apiClient.ContainerStatPath(ctx, copyConfig.container, linkTarget)
// FIXME(thaJeztah): unhandled error (should this return?)
}
// Validate the destination path
if err := command.ValidateOutputPathFileMode(dstStat.Mode); err != nil {
return errors.Wrapf(err, `destination "%s:%s" must be a directory or a regular file`, copyConfig.container, dstPath)
}
// Ignore any error and assume that the parent directory of the destination
// path exists, in which case the copy may still succeed. If there is any
// type of conflict (e.g., non-directory overwriting an existing directory
// or vice versa) the extraction will fail. If the destination simply did
// not exist, but the parent directory does, the extraction will still
// succeed.
if err == nil {
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
// Ignore any error and assume that the parent directory of the destination
// path exists, in which case the copy may still succeed. If there is any
// type of conflict (e.g., non-directory overwriting an existing directory
// or vice versa) the extraction will fail. If the destination simply did
// not exist, but the parent directory does, the extraction will still
// succeed.
_ = err // Intentionally ignore stat errors (see above)
}
var (
@ -354,7 +360,7 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
content = os.Stdin
resolvedDstPath = dstInfo.Path
if !dstInfo.IsDir {
return errors.Errorf("destination \"%s:%s\" must be a directory", copyConfig.container, dstPath)
return fmt.Errorf(`destination "%s:%s" must be a directory`, copyConfig.container, dstPath)
}
} else {
// Prepare source copy info.
@ -397,24 +403,27 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
}
}
options := container.CopyToContainerOptions{
AllowOverwriteDirWithFile: false,
CopyUIDGID: copyConfig.copyUIDGID,
options := client.CopyToContainerOptions{
DestinationPath: resolvedDstPath,
Content: content,
CopyUIDGID: copyConfig.copyUIDGID,
}
if copyConfig.quiet {
return apiClient.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, options)
_, err := apiClient.CopyToContainer(ctx, copyConfig.container, options)
return err
}
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
restore, done := copyProgress(ctx, dockerCLI.Err(), copyToContainerHeader, &copiedSize)
res := apiClient.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, options)
// TODO(thaJeztah): error-handling looks odd here; should it be handled differently?
_, err := apiClient.CopyToContainer(ctx, copyConfig.container, options)
cancel()
<-done
restore()
fmt.Fprintln(dockerCLI.Err(), "Successfully copied", progressHumanSize(copiedSize), "to", copyConfig.container+":"+dstInfo.Path)
_, _ = fmt.Fprintln(dockerCLI.Err(), "Successfully copied", progressHumanSize(copiedSize), "to", copyConfig.container+":"+dstInfo.Path)
return res
return err
}
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be

View File

@ -9,9 +9,9 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/container"
"github.com/moby/go-archive"
"github.com/moby/go-archive/compression"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/fs"
@ -52,9 +52,11 @@ func TestRunCopyFromContainerToStdout(t *testing.T) {
tarContent := "the tar content"
cli := test.NewFakeCli(&fakeClient{
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
containerCopyFromFunc: func(ctr, srcPath string) (client.CopyFromContainerResult, error) {
assert.Check(t, is.Equal("container", ctr))
return io.NopCloser(strings.NewReader(tarContent)), container.PathStat{}, nil
return client.CopyFromContainerResult{
Content: io.NopCloser(strings.NewReader(tarContent)),
}, nil
},
})
err := runCopy(context.TODO(), cli, copyOptions{
@ -73,10 +75,12 @@ func TestRunCopyFromContainerToFilesystem(t *testing.T) {
destDir := fs.NewDir(t, "cp-test")
cli := test.NewFakeCli(&fakeClient{
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
containerCopyFromFunc: func(ctr, srcPath string) (client.CopyFromContainerResult, error) {
assert.Check(t, is.Equal("container", ctr))
readCloser, err := archive.Tar(srcDir.Path(), compression.None)
return readCloser, container.PathStat{}, err
return client.CopyFromContainerResult{
Content: readCloser,
}, err
},
})
err := runCopy(context.TODO(), cli, copyOptions{
@ -99,10 +103,12 @@ func TestRunCopyFromContainerToFilesystemMissingDestinationDirectory(t *testing.
defer destDir.Remove()
cli := test.NewFakeCli(&fakeClient{
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
containerCopyFromFunc: func(ctr, srcPath string) (client.CopyFromContainerResult, error) {
assert.Check(t, is.Equal("container", ctr))
readCloser, err := archive.TarWithOptions(destDir.Path(), &archive.TarOptions{})
return readCloser, container.PathStat{}, err
return client.CopyFromContainerResult{
Content: readCloser,
}, err
},
})
err := runCopy(context.TODO(), cli, copyOptions{

View File

@ -4,33 +4,27 @@ import (
"archive/tar"
"bytes"
"context"
"errors"
"fmt"
"io"
"net/netip"
"os"
"path"
"strings"
cerrdefs "github.com/containerd/errdefs"
"github.com/containerd/errdefs"
"github.com/containerd/platforms"
"github.com/distribution/reference"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/types"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/jsonstream"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
imagetypes "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/moby/moby/api/types/mount"
"github.com/moby/moby/client"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@ -45,14 +39,13 @@ const (
type createOptions struct {
name string
platform string
untrusted bool
pull string // always, missing, never
quiet bool
useAPISocket bool
}
// NewCreateCommand creates a new cobra.Command for `docker create`
func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
// newCreateCommand creates a new cobra.Command for `docker create`
func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
var options createOptions
var copts *containerOptions
@ -65,12 +58,13 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
if len(args) > 1 {
copts.Args = args[1:]
}
return runCreate(cmd.Context(), dockerCli, cmd.Flags(), &options, copts)
return runCreate(cmd.Context(), dockerCLI, cmd.Flags(), &options, copts)
},
Annotations: map[string]string{
"aliases": "docker container create, docker create",
},
ValidArgsFunction: completion.ImageNames(dockerCli, -1),
ValidArgsFunction: completion.ImageNames(dockerCLI, -1),
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -80,24 +74,23 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
flags.StringVar(&options.pull, "pull", PullImageMissing, `Pull image before creating ("`+PullImageAlways+`", "|`+PullImageMissing+`", "`+PullImageNever+`")`)
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the pull output")
flags.BoolVarP(&options.useAPISocket, "use-api-socket", "", false, "Bind mount Docker API socket and required auth")
flags.SetAnnotation("use-api-socket", "experimentalCLI", nil) // Marks flag as experimental for now.
_ = flags.SetAnnotation("use-api-socket", "experimentalCLI", nil) // Mark flag as experimental for now.
// Add an explicit help that doesn't have a `-h` to prevent the conflict
// with hostname
flags.Bool("help", false, "Print usage")
command.AddPlatformFlag(flags, &options.platform)
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
// TODO(thaJeztah): consider adding platform as "image create option" on containerOptions
flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
_ = flags.SetAnnotation("platform", "version", []string{"1.32"})
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms())
// TODO(thaJeztah): DEPRECATED: remove in v29.1 or v30
flags.Bool("disable-content-trust", true, "Skip image verification (deprecated)")
_ = flags.MarkDeprecated("disable-content-trust", "support for docker content trust was removed")
copts = addFlags(flags)
addCompletions(cmd, dockerCli)
flags.VisitAll(func(flag *pflag.Flag) {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
})
addCompletions(cmd, dockerCLI)
return cmd
}
@ -110,7 +103,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
}
}
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
newEnv := []string{}
newEnv := make([]string, 0, len(proxyConfig))
for k, v := range proxyConfig {
if v == nil {
newEnv = append(newEnv, k)
@ -141,20 +134,27 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
return err
}
responseBody, err := dockerCli.Client().ImageCreate(ctx, img, imagetypes.CreateOptions{
var ociPlatforms []ocispec.Platform
if options.platform != "" {
// Already validated.
ociPlatforms = append(ociPlatforms, platforms.MustParse(options.platform))
}
resp, err := dockerCli.Client().ImagePull(ctx, img, client.ImagePullOptions{
RegistryAuth: encodedAuth,
Platform: options.platform,
Platforms: ociPlatforms,
})
if err != nil {
return err
}
defer responseBody.Close()
defer func() {
_ = resp.Close()
}()
out := dockerCli.Err()
if options.quiet {
out = streams.NewOut(io.Discard)
}
return jsonstream.Display(ctx, responseBody, out)
return jsonstream.Display(ctx, resp, out)
}
type cidFile struct {
@ -167,13 +167,13 @@ func (cid *cidFile) Close() error {
if cid.file == nil {
return nil
}
cid.file.Close()
_ = cid.file.Close()
if cid.written {
return nil
}
if err := os.Remove(cid.path); err != nil {
return errors.Wrapf(err, "failed to remove the CID file '%s'", cid.path)
return fmt.Errorf("failed to remove the CID file '%s': %w", cid.path, err)
}
return nil
@ -184,7 +184,7 @@ func (cid *cidFile) Write(id string) error {
return nil
}
if _, err := cid.file.Write([]byte(id)); err != nil {
return errors.Wrap(err, "failed to write the container ID to the file")
return fmt.Errorf("failed to write the container ID to the file: %w", err)
}
cid.written = true
return nil
@ -195,12 +195,12 @@ func newCIDFile(cidPath string) (*cidFile, error) {
return &cidFile{}, nil
}
if _, err := os.Stat(cidPath); err == nil {
return nil, errors.Errorf("container ID file found, make sure the other container isn't running or delete %s", cidPath)
return nil, errors.New("container ID file found, make sure the other container isn't running or delete " + cidPath)
}
f, err := os.Create(cidPath)
if err != nil {
return nil, errors.Wrap(err, "failed to create the container ID file")
return nil, fmt.Errorf("failed to create the container ID file: %w", err)
}
return &cidFile{path: cidPath, file: f}, nil
@ -212,16 +212,23 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
hostConfig := containerCfg.HostConfig
networkingConfig := containerCfg.NetworkingConfig
var (
trustedRef reference.Canonical
namedRef reference.Named
)
var namedRef reference.Named
// TODO(thaJeztah): add a platform option-type / flag-type.
if options.platform != "" {
_, err = platforms.Parse(options.platform)
if err != nil {
return "", err
}
}
containerIDFile, err := newCIDFile(hostConfig.ContainerIDFile)
if err != nil {
return "", err
}
defer containerIDFile.Close()
defer func() {
_ = containerIDFile.Close()
}()
ref, err := reference.ParseAnyReference(config.Image)
if err != nil {
@ -229,15 +236,6 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
}
if named, ok := ref.(reference.Named); ok {
namedRef = reference.TagNameOnly(named)
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && !options.untrusted {
var err error
trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef)
if err != nil {
return "", err
}
config.Image = reference.FamiliarString(trustedRef)
}
}
const dockerConfigPathInContainer = "/run/secrets/docker/config.json"
@ -248,15 +246,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{},
})
@ -309,14 +306,10 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
}
var platform *ocispec.Platform
// Engine API version 1.41 first introduced the option to specify platform on
// create. It will produce an error if you try to set a platform on older API
// versions, so check the API version here to maintain backwards
// compatibility for CLI users.
if options.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") {
if options.platform != "" {
p, err := platforms.Parse(options.platform)
if err != nil {
return "", errors.Wrap(invalidParameter(err), "error parsing specified platform")
return "", invalidParameter(fmt.Errorf("error parsing specified platform: %w", err))
}
platform = &p
}
@ -325,9 +318,6 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
if err := pullImage(ctx, dockerCli, config.Image, options); err != nil {
return err
}
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
return trust.TagTrusted(ctx, dockerCli.Client(), dockerCli.Err(), trustedRef, taggedRef)
}
return nil
}
@ -339,10 +329,17 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name)
response, err := dockerCli.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
Name: options.name,
// Image: config.Image, // TODO(thaJeztah): pass image-ref separate
Platform: platform,
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
})
if err != nil {
// Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior.
if cerrdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing {
if errdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing {
if !options.quiet {
// we don't want to write to stdout anything apart from container.ID
_, _ = fmt.Fprintf(dockerCli.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
@ -353,7 +350,14 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
}
var retryErr error
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name)
response, retryErr = dockerCli.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
Name: options.name,
// Image: config.Image, // TODO(thaJeztah): pass image-ref separate
Platform: platform,
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
})
if retryErr != nil {
return "", retryErr
}
@ -362,10 +366,6 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
}
}
if warn := localhostDNSWarning(*hostConfig); warn != "" {
response.Warnings = append(response.Warnings, warn)
}
containerID = response.ID
for _, w := range response.Warnings {
_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
@ -386,19 +386,6 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
return containerID, err
}
// check the DNS settings passed via --dns against localhost regexp to warn if
// they are trying to set a DNS to a localhost address.
//
// TODO(thaJeztah): move this to the daemon, which can make a better call if it will work or not (depending on networking mode).
func localhostDNSWarning(hostConfig container.HostConfig) string {
for _, dnsIP := range hostConfig.DNS {
if addr, err := netip.ParseAddr(dnsIP); err == nil && addr.IsLoopback() {
return fmt.Sprintf("Localhost DNS (%s) may fail in containers.", addr)
}
}
return ""
}
func validatePullOpt(val string) error {
switch val {
case PullImageAlways, PullImageMissing, PullImageNever, "":
@ -420,7 +407,7 @@ func validatePullOpt(val string) error {
//
// The path should be an absolute path in the container, commonly
// /root/.docker/config.json.
func copyDockerConfigIntoContainer(ctx context.Context, dockerAPI client.APIClient, containerID string, configPath string, config *configfile.ConfigFile) error {
func copyDockerConfigIntoContainer(ctx context.Context, apiClient client.APIClient, containerID string, configPath string, config *configfile.ConfigFile) error {
var configBuf bytes.Buffer
if err := config.SaveToWriter(&configBuf); err != nil {
return fmt.Errorf("saving creds: %w", err)
@ -429,7 +416,7 @@ func copyDockerConfigIntoContainer(ctx context.Context, dockerAPI client.APIClie
// We don't need to get super fancy with the tar creation.
var tarBuf bytes.Buffer
tarWriter := tar.NewWriter(&tarBuf)
tarWriter.WriteHeader(&tar.Header{
_ = tarWriter.WriteHeader(&tar.Header{
Name: configPath,
Size: int64(configBuf.Len()),
Mode: 0o600,
@ -443,8 +430,11 @@ func copyDockerConfigIntoContainer(ctx context.Context, dockerAPI client.APIClie
return fmt.Errorf("closing tar for config copy failed: %w", err)
}
if err := dockerAPI.CopyToContainer(ctx, containerID, "/",
&tarBuf, container.CopyToContainerOptions{}); err != nil {
_, err := apiClient.CopyToContainer(ctx, containerID, client.CopyToContainerOptions{
DestinationPath: "/",
Content: &tarBuf,
})
if err != nil {
return fmt.Errorf("copying config.json into container failed: %w", err)
}

View File

@ -13,13 +13,10 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/notary"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/system"
"github.com/google/go-cmp/cmp"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
"github.com/spf13/pflag"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -116,36 +113,31 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
t.Run(tc.PullPolicy, func(t *testing.T) {
pullCounter := 0
client := &fakeClient{
createContainerFunc: func(
config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string,
) (container.CreateResponse, error) {
apiClient := &fakeClient{
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
defer func() { tc.ResponseCounter++ }()
switch tc.ResponseCounter {
case 0:
return container.CreateResponse{}, fakeNotFound{}
return client.ContainerCreateResult{}, fakeNotFound{}
default:
return container.CreateResponse{ID: containerID}, nil
return client.ContainerCreateResult{ID: containerID}, nil
}
},
imageCreateFunc: func(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
imagePullFunc: func(ctx context.Context, parentReference string, options client.ImagePullOptions) (client.ImagePullResponse, error) {
defer func() { pullCounter++ }()
return io.NopCloser(strings.NewReader("")), nil
return fakeStreamResult{ReadCloser: io.NopCloser(strings.NewReader(""))}, nil
},
infoFunc: func() (system.Info, error) {
return system.Info{IndexServerAddress: "https://indexserver.example.com"}, nil
infoFunc: func() (client.SystemInfoResult, error) {
return client.SystemInfoResult{
Info: system.Info{IndexServerAddress: "https://indexserver.example.com"},
}, nil
},
}
fakeCLI := test.NewFakeCli(client)
fakeCLI := test.NewFakeCli(apiClient)
id, err := createContainer(context.Background(), fakeCLI, config, &createOptions{
name: "name",
platform: runtime.GOOS,
untrusted: true,
pull: tc.PullPolicy,
name: "name",
platform: runtime.GOOS,
pull: tc.PullPolicy,
})
if tc.ExpectedErrMsg != "" {
@ -206,7 +198,7 @@ func TestCreateContainerValidateFlags(t *testing.T) {
},
} {
t.Run(tc.name, func(t *testing.T) {
cmd := NewCreateCommand(test.NewFakeCli(&fakeClient{}))
cmd := newCreateCommand(test.NewFakeCli(&fakeClient{}))
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs(tc.args)
@ -221,55 +213,6 @@ func TestCreateContainerValidateFlags(t *testing.T) {
}
}
func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
notaryFunc test.NotaryClientFuncType
}{
{
name: "offline-notary-server",
notaryFunc: notary.GetOfflineNotaryRepository,
expectedError: "client is offline",
args: []string{"image:tag"},
},
{
name: "uninitialized-notary-server",
notaryFunc: notary.GetUninitializedNotaryRepository,
expectedError: "remote trust data does not exist",
args: []string{"image:tag"},
},
{
name: "empty-notary-server",
notaryFunc: notary.GetEmptyTargetsNotaryRepository,
expectedError: "No valid trust data for tag",
args: []string{"image:tag"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string,
) (container.CreateResponse, error) {
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
},
}, test.EnableContentTrust)
fakeCLI.SetNotaryClient(tc.notaryFunc)
cmd := NewCreateCommand(fakeCLI)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.ErrorContains(t, err, tc.expectedError)
})
}
}
func TestNewCreateCommandWithWarnings(t *testing.T) {
testCases := []struct {
name string
@ -291,30 +234,15 @@ func TestNewCreateCommandWithWarnings(t *testing.T) {
args: []string{"image:tag"},
warnings: []string{"warning from daemon", "another warning from daemon"},
},
{
name: "container-create-localhost-dns",
args: []string{"--dns=127.0.0.11", "image:tag"},
warning: true,
},
{
name: "container-create-localhost-dns-ipv6",
args: []string{"--dns=::1", "image:tag"},
warning: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string,
) (container.CreateResponse, error) {
return container.CreateResponse{Warnings: tc.warnings}, nil
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
return client.ContainerCreateResult{Warnings: tc.warnings}, nil
},
})
cmd := NewCreateCommand(fakeCLI)
cmd := newCreateCommand(fakeCLI)
cmd.SetOut(io.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
@ -344,15 +272,10 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
sort.Strings(expected)
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string,
) (container.CreateResponse, error) {
sort.Strings(config.Env)
assert.DeepEqual(t, config.Env, expected)
return container.CreateResponse{}, nil
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
sort.Strings(options.Config.Env)
assert.DeepEqual(t, options.Config.Env, expected)
return client.ContainerCreateResult{}, nil
},
})
fakeCLI.SetConfigFile(&configfile.ConfigFile{
@ -366,7 +289,7 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
},
},
})
cmd := NewCreateCommand(fakeCLI)
cmd := newCreateCommand(fakeCLI)
cmd.SetOut(io.Discard)
cmd.SetArgs([]string{"image:tag"})
err := cmd.Execute()

View File

@ -7,44 +7,35 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
"github.com/pkg/errors"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
type diffOptions struct {
container string
}
// NewDiffCommand creates a new cobra.Command for `docker diff`
func NewDiffCommand(dockerCli command.Cli) *cobra.Command {
var opts diffOptions
// newDiffCommand creates a new cobra.Command for `docker diff`
func newDiffCommand(dockerCLI command.Cli) *cobra.Command {
return &cobra.Command{
Use: "diff CONTAINER",
Short: "Inspect changes to files or directories on a container's filesystem",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.container = args[0]
return runDiff(cmd.Context(), dockerCli, &opts)
return runDiff(cmd.Context(), dockerCLI, args[0])
},
Annotations: map[string]string{
"aliases": "docker container diff, docker diff",
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
DisableFlagsInUseLine: true,
}
}
func runDiff(ctx context.Context, dockerCli command.Cli, opts *diffOptions) error {
if opts.container == "" {
return errors.New("Container name cannot be empty")
}
changes, err := dockerCli.Client().ContainerDiff(ctx, opts.container)
func runDiff(ctx context.Context, dockerCLI command.Cli, containerID string) error {
res, err := dockerCLI.Client().ContainerDiff(ctx, containerID, client.ContainerDiffOptions{})
if err != nil {
return err
}
diffCtx := formatter.Context{
Output: dockerCli.Out(),
Format: NewDiffFormat("{{.Type}} {{.Path}}"),
Output: dockerCLI.Out(),
Format: newDiffFormat("{{.Type}} {{.Path}}"),
}
return DiffFormatWrite(diffCtx, changes)
return diffFormatWrite(diffCtx, res)
}

View File

@ -8,35 +8,35 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestRunDiff(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerDiffFunc: func(
ctx context.Context,
containerID string,
) ([]container.FilesystemChange, error) {
return []container.FilesystemChange{
{
Kind: container.ChangeModify,
Path: "/path/to/file0",
},
{
Kind: container.ChangeAdd,
Path: "/path/to/file1",
},
{
Kind: container.ChangeDelete,
Path: "/path/to/file2",
containerDiffFunc: func(ctx context.Context, containerID string) (client.ContainerDiffResult, error) {
return client.ContainerDiffResult{
Changes: []container.FilesystemChange{
{
Kind: container.ChangeModify,
Path: "/path/to/file0",
},
{
Kind: container.ChangeAdd,
Path: "/path/to/file1",
},
{
Kind: container.ChangeDelete,
Path: "/path/to/file2",
},
},
}, nil
},
})
cmd := NewDiffCommand(cli)
cmd := newDiffCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs([]string{"container-id"})
@ -60,15 +60,12 @@ func TestRunDiffClientError(t *testing.T) {
clientError := errors.New("client error")
cli := test.NewFakeCli(&fakeClient{
containerDiffFunc: func(
ctx context.Context,
containerID string,
) ([]container.FilesystemChange, error) {
return nil, clientError
containerDiffFunc: func(ctx context.Context, containerID string) (client.ContainerDiffResult, error) {
return client.ContainerDiffResult{}, clientError
},
})
cmd := NewDiffCommand(cli)
cmd := newDiffCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
@ -77,17 +74,3 @@ func TestRunDiffClientError(t *testing.T) {
err := cmd.Execute()
assert.ErrorIs(t, err, clientError)
}
func TestRunDiffEmptyContainerError(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cmd := NewDiffCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
containerID := ""
cmd.SetArgs([]string{containerID})
err := cmd.Execute()
assert.Error(t, err, "Container name cannot be empty")
}

View File

@ -1,9 +1,9 @@
package container
import cerrdefs "github.com/containerd/errdefs"
import "github.com/containerd/errdefs"
func invalidParameter(err error) error {
if err == nil || cerrdefs.IsInvalidArgument(err) {
if err == nil || errdefs.IsInvalidArgument(err) {
return err
}
return invalidParameterErr{err}
@ -17,7 +17,7 @@ func (e invalidParameterErr) Unwrap() error {
}
func notFound(err error) error {
if err == nil || cerrdefs.IsNotFound(err) {
if err == nil || errdefs.IsNotFound(err) {
return err
}
return notFoundErr{err}

View File

@ -2,6 +2,7 @@ package container
import (
"context"
"errors"
"fmt"
"io"
@ -10,9 +11,8 @@ import (
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@ -39,8 +39,8 @@ func NewExecOptions() ExecOptions {
}
}
// NewExecCommand creates a new cobra.Command for `docker exec`
func NewExecCommand(dockerCli command.Cli) *cobra.Command {
// newExecCommand creates a new cobra.Command for "docker exec".
func newExecCommand(dockerCLI command.Cli) *cobra.Command {
options := NewExecOptions()
cmd := &cobra.Command{
@ -50,15 +50,16 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
containerIDorName := args[0]
options.Command = args[1:]
return RunExec(cmd.Context(), dockerCli, containerIDorName, options)
return RunExec(cmd.Context(), dockerCLI, containerIDorName, options)
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(ctr container.Summary) bool {
ValidArgsFunction: completion.ContainerNames(dockerCLI, false, func(ctr container.Summary) bool {
return ctr.State != container.StatePaused
}),
Annotations: map[string]string{
"category-top": "2",
"aliases": "docker container exec, docker exec",
},
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -71,14 +72,14 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
flags.StringVarP(&options.User, "user", "u", "", `Username or UID (format: "<name|uid>[:<group|gid>]")`)
flags.BoolVar(&options.Privileged, "privileged", false, "Give extended privileges to the command")
flags.VarP(&options.Env, "env", "e", "Set environment variables")
flags.SetAnnotation("env", "version", []string{"1.25"})
_ = flags.SetAnnotation("env", "version", []string{"1.25"})
flags.Var(&options.EnvFile, "env-file", "Read in a file of environment variables")
flags.SetAnnotation("env-file", "version", []string{"1.25"})
_ = flags.SetAnnotation("env-file", "version", []string{"1.25"})
flags.StringVarP(&options.Workdir, "workdir", "w", "", "Working directory inside the container")
flags.SetAnnotation("workdir", "version", []string{"1.35"})
_ = flags.SetAnnotation("workdir", "version", []string{"1.35"})
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames())
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames())
return cmd
}
@ -96,18 +97,18 @@ func RunExec(ctx context.Context, dockerCLI command.Cli, containerIDorName strin
// otherwise if we error out we will leak execIDs on the server (and
// there's no easy way to clean those up). But also in order to make "not
// exist" errors take precedence we do a dummy inspect first.
if _, err := apiClient.ContainerInspect(ctx, containerIDorName); err != nil {
if _, err := apiClient.ContainerInspect(ctx, containerIDorName, client.ContainerInspectOptions{}); err != nil {
return err
}
if !execOptions.Detach {
if err := dockerCLI.In().CheckTty(execOptions.AttachStdin, execOptions.Tty); err != nil {
if !options.Detach {
if err := dockerCLI.In().CheckTty(execOptions.AttachStdin, execOptions.TTY); err != nil {
return err
}
}
fillConsoleSize(execOptions, dockerCLI)
response, err := apiClient.ContainerExecCreate(ctx, containerIDorName, *execOptions)
response, err := apiClient.ExecCreate(ctx, containerIDorName, *execOptions)
if err != nil {
return err
}
@ -117,24 +118,25 @@ func RunExec(ctx context.Context, dockerCLI command.Cli, containerIDorName strin
return errors.New("exec ID empty")
}
if execOptions.Detach {
return apiClient.ContainerExecStart(ctx, execID, container.ExecStartOptions{
Detach: execOptions.Detach,
Tty: execOptions.Tty,
ConsoleSize: execOptions.ConsoleSize,
if options.Detach {
_, err := apiClient.ExecStart(ctx, execID, client.ExecStartOptions{
Detach: options.Detach,
TTY: execOptions.TTY,
ConsoleSize: client.ConsoleSize{Height: execOptions.ConsoleSize.Height, Width: execOptions.ConsoleSize.Width},
})
return err
}
return interactiveExec(ctx, dockerCLI, execOptions, execID)
}
func fillConsoleSize(execOptions *container.ExecOptions, dockerCli command.Cli) {
if execOptions.Tty {
func fillConsoleSize(execOptions *client.ExecCreateOptions, dockerCli command.Cli) {
if execOptions.TTY {
height, width := dockerCli.Out().GetTtySize()
execOptions.ConsoleSize = &[2]uint{height, width}
execOptions.ConsoleSize = client.ConsoleSize{Height: height, Width: width}
}
}
func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *container.ExecOptions, execID string) error {
func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *client.ExecCreateOptions, execID string) error {
// Interactive exec requested.
var (
out, stderr io.Writer
@ -148,7 +150,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *co
out = dockerCli.Out()
}
if execOptions.AttachStderr {
if execOptions.Tty {
if execOptions.TTY {
stderr = dockerCli.Out()
} else {
stderr = dockerCli.Err()
@ -157,9 +159,9 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *co
fillConsoleSize(execOptions, dockerCli)
apiClient := dockerCli.Client()
resp, err := apiClient.ContainerExecAttach(ctx, execID, container.ExecAttachOptions{
Tty: execOptions.Tty,
ConsoleSize: execOptions.ConsoleSize,
resp, err := apiClient.ExecAttach(ctx, execID, client.ExecAttachOptions{
TTY: execOptions.TTY,
ConsoleSize: client.ConsoleSize{Height: execOptions.ConsoleSize.Height, Width: execOptions.ConsoleSize.Width},
})
if err != nil {
return err
@ -176,8 +178,8 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *co
inputStream: in,
outputStream: out,
errorStream: stderr,
resp: resp,
tty: execOptions.Tty,
resp: resp.HijackedResponse,
tty: execOptions.TTY,
detachKeys: execOptions.DetachKeys,
}
@ -185,7 +187,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *co
}()
}()
if execOptions.Tty && dockerCli.In().IsTerminal() {
if execOptions.TTY && dockerCli.In().IsTerminal() {
if err := MonitorTtySize(ctx, dockerCli, execID, true); err != nil {
_, _ = fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
}
@ -199,8 +201,8 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *co
return getExecExitStatus(ctx, apiClient, execID)
}
func getExecExitStatus(ctx context.Context, apiClient client.ContainerAPIClient, execID string) error {
resp, err := apiClient.ContainerExecInspect(ctx, execID)
func getExecExitStatus(ctx context.Context, apiClient client.ExecAPIClient, execID string) error {
resp, err := apiClient.ExecInspect(ctx, execID, client.ExecInspectOptions{})
if err != nil {
// If we can't connect, then the daemon probably died.
if !client.IsErrConnectionFailed(err) {
@ -217,13 +219,12 @@ func getExecExitStatus(ctx context.Context, apiClient client.ContainerAPIClient,
// parseExec parses the specified args for the specified command and generates
// an ExecConfig from it.
func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*container.ExecOptions, error) {
execOptions := &container.ExecOptions{
func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*client.ExecCreateOptions, error) {
execOptions := &client.ExecCreateOptions{
User: execOpts.User,
Privileged: execOpts.Privileged,
Tty: execOpts.TTY,
TTY: execOpts.TTY,
Cmd: execOpts.Command,
Detach: execOpts.Detach,
WorkingDir: execOpts.Workdir,
}

View File

@ -5,13 +5,14 @@ import (
"errors"
"io"
"os"
"strconv"
"testing"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/fs"
@ -37,10 +38,10 @@ TWO=2
testcases := []struct {
options ExecOptions
configFile configfile.ConfigFile
expected container.ExecOptions
expected client.ExecCreateOptions
}{
{
expected: container.ExecOptions{
expected: client.ExecCreateOptions{
Cmd: []string{"command"},
AttachStdout: true,
AttachStderr: true,
@ -48,7 +49,7 @@ TWO=2
options: withDefaultOpts(ExecOptions{}),
},
{
expected: container.ExecOptions{
expected: client.ExecCreateOptions{
Cmd: []string{"command1", "command2"},
AttachStdout: true,
AttachStderr: true,
@ -63,20 +64,19 @@ TWO=2
TTY: true,
User: "uid",
}),
expected: container.ExecOptions{
expected: client.ExecCreateOptions{
User: "uid",
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: true,
TTY: true,
Cmd: []string{"command"},
},
},
{
options: withDefaultOpts(ExecOptions{Detach: true}),
expected: container.ExecOptions{
Detach: true,
Cmd: []string{"command"},
expected: client.ExecCreateOptions{
Cmd: []string{"command"},
},
},
{
@ -85,19 +85,17 @@ TWO=2
Interactive: true,
Detach: true,
}),
expected: container.ExecOptions{
Detach: true,
Tty: true,
Cmd: []string{"command"},
expected: client.ExecCreateOptions{
TTY: true,
Cmd: []string{"command"},
},
},
{
options: withDefaultOpts(ExecOptions{Detach: true}),
configFile: configfile.ConfigFile{DetachKeys: "de"},
expected: container.ExecOptions{
expected: client.ExecCreateOptions{
Cmd: []string{"command"},
DetachKeys: "de",
Detach: true,
},
},
{
@ -106,14 +104,13 @@ TWO=2
DetachKeys: "ab",
}),
configFile: configfile.ConfigFile{DetachKeys: "de"},
expected: container.ExecOptions{
expected: client.ExecCreateOptions{
Cmd: []string{"command"},
DetachKeys: "ab",
Detach: true,
},
},
{
expected: container.ExecOptions{
expected: client.ExecCreateOptions{
Cmd: []string{"command"},
AttachStdout: true,
AttachStderr: true,
@ -121,12 +118,12 @@ TWO=2
},
options: func() ExecOptions {
o := withDefaultOpts(ExecOptions{})
o.EnvFile.Set(tmpFile.Path())
_ = o.EnvFile.Set(tmpFile.Path())
return o
}(),
},
{
expected: container.ExecOptions{
expected: client.ExecCreateOptions{
Cmd: []string{"command"},
AttachStdout: true,
AttachStderr: true,
@ -134,23 +131,25 @@ TWO=2
},
options: func() ExecOptions {
o := withDefaultOpts(ExecOptions{})
o.EnvFile.Set(tmpFile.Path())
o.Env.Set("ONE=override")
_ = o.EnvFile.Set(tmpFile.Path())
_ = o.Env.Set("ONE=override")
return o
}(),
},
}
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))
})
}
}
func TestParseExecNoSuchFile(t *testing.T) {
execOpts := withDefaultOpts(ExecOptions{})
execOpts.EnvFile.Set("no-such-env-file")
assert.Check(t, execOpts.EnvFile.Set("no-such-env-file"))
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
assert.ErrorContains(t, err, "no-such-env-file")
assert.Check(t, os.IsNotExist(err))
@ -177,8 +176,8 @@ func TestRunExec(t *testing.T) {
doc: "inspect error",
options: NewExecOptions(),
client: &fakeClient{
inspectFunc: func(string) (container.InspectResponse, error) {
return container.InspectResponse{}, errors.New("failed inspect")
inspectFunc: func(string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{}, errors.New("failed inspect")
},
},
expectedError: "failed inspect",
@ -195,7 +194,7 @@ func TestRunExec(t *testing.T) {
t.Run(testcase.doc, func(t *testing.T) {
fakeCLI := test.NewFakeCli(testcase.client)
err := RunExec(context.TODO(), fakeCLI, "thecontainer", testcase.options)
err := RunExec(context.TODO(), fakeCLI, "the-container", testcase.options)
if testcase.expectedError != "" {
assert.ErrorContains(t, err, testcase.expectedError)
} else if !assert.Check(t, err) {
@ -207,8 +206,8 @@ func TestRunExec(t *testing.T) {
}
}
func execCreateWithID(_ string, _ container.ExecOptions) (container.ExecCreateResponse, error) {
return container.ExecCreateResponse{ID: "execid"}, nil
func execCreateWithID(_ string, _ client.ExecCreateOptions) (client.ExecCreateResult, error) {
return client.ExecCreateResult{ID: "exec-id"}, nil
}
func TestGetExecExitStatus(t *testing.T) {
@ -235,13 +234,13 @@ func TestGetExecExitStatus(t *testing.T) {
}
for _, testcase := range testcases {
client := &fakeClient{
execInspectFunc: func(id string) (container.ExecInspect, error) {
apiClient := &fakeClient{
execInspectFunc: func(id string) (client.ExecInspectResult, error) {
assert.Check(t, is.Equal(execID, id))
return container.ExecInspect{ExitCode: testcase.exitCode}, testcase.inspectError
return client.ExecInspectResult{ExitCode: testcase.exitCode}, testcase.inspectError
},
}
err := getExecExitStatus(context.Background(), client, execID)
err := getExecExitStatus(context.Background(), apiClient, execID)
assert.Check(t, is.Equal(testcase.expectedError, err))
}
}
@ -251,20 +250,20 @@ func TestNewExecCommandErrors(t *testing.T) {
name string
args []string
expectedError string
containerInspectFunc func(img string) (container.InspectResponse, error)
containerInspectFunc func(img string) (client.ContainerInspectResult, error)
}{
{
name: "client-error",
args: []string{"5cb5bb5e4a3b", "-t", "-i", "bash"},
expectedError: "something went wrong",
containerInspectFunc: func(containerID string) (container.InspectResponse, error) {
return container.InspectResponse{}, errors.New("something went wrong")
containerInspectFunc: func(containerID string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{}, errors.New("something went wrong")
},
},
}
for _, tc := range testCases {
fakeCLI := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})
cmd := NewExecCommand(fakeCLI)
cmd := newExecCommand(fakeCLI)
cmd.SetOut(io.Discard)
cmd.SetArgs(tc.args)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)

View File

@ -2,13 +2,15 @@ package container
import (
"context"
"errors"
"fmt"
"io"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/moby/moby/client"
"github.com/moby/sys/atomicwriter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -17,8 +19,8 @@ type exportOptions struct {
output string
}
// NewExportCommand creates a new `docker export` command
func NewExportCommand(dockerCli command.Cli) *cobra.Command {
// newExportCommand creates a new "docker container export" command.
func newExportCommand(dockerCLI command.Cli) *cobra.Command {
var opts exportOptions
cmd := &cobra.Command{
@ -27,12 +29,13 @@ func NewExportCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.container = args[0]
return runExport(cmd.Context(), dockerCli, opts)
return runExport(cmd.Context(), dockerCLI, opts)
},
Annotations: map[string]string{
"aliases": "docker container export, docker export",
},
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
ValidArgsFunction: completion.ContainerNames(dockerCLI, true),
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -52,13 +55,13 @@ func runExport(ctx context.Context, dockerCLI command.Cli, opts exportOptions) e
} else {
writer, err := atomicwriter.New(opts.output, 0o600)
if err != nil {
return errors.Wrap(err, "failed to export container")
return fmt.Errorf("failed to export container: %w", err)
}
defer writer.Close()
output = writer
}
responseBody, err := dockerCLI.Client().ContainerExport(ctx, opts.container)
responseBody, err := dockerCLI.Client().ContainerExport(ctx, opts.container, client.ContainerExportOptions{})
if err != nil {
return err
}

View File

@ -2,10 +2,10 @@ package container
import (
"io"
"strings"
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
"gotest.tools/v3/fs"
)
@ -15,11 +15,12 @@ func TestContainerExportOutputToFile(t *testing.T) {
defer dir.Remove()
cli := test.NewFakeCli(&fakeClient{
containerExportFunc: func(container string) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("bar")), nil
containerExportFunc: func(container string) (client.ContainerExportResult, error) {
// FIXME(thaJeztah): how to mock this?
return mockContainerExportResult("bar"), nil
},
})
cmd := NewExportCommand(cli)
cmd := newExportCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs([]string{"-o", dir.Join("foo"), "container"})
assert.NilError(t, cmd.Execute())
@ -33,11 +34,12 @@ func TestContainerExportOutputToFile(t *testing.T) {
func TestContainerExportOutputToIrregularFile(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerExportFunc: func(container string) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("foo")), nil
containerExportFunc: func(container string) (client.ContainerExportResult, error) {
// FIXME(thaJeztah): how to mock this?
return mockContainerExportResult("foo"), nil
},
})
cmd := NewExportCommand(cli)
cmd := newExportCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs([]string{"-o", "/dev/random", "container"})

View File

@ -2,7 +2,8 @@ package container
import (
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
)
const (
@ -12,25 +13,24 @@ const (
pathHeader = "PATH"
)
// NewDiffFormat returns a format for use with a diff Context
func NewDiffFormat(source string) formatter.Format {
// newDiffFormat returns a format for use with a diff [formatter.Context].
func newDiffFormat(source string) formatter.Format {
if source == formatter.TableFormatKey {
return defaultDiffTableFormat
}
return formatter.Format(source)
}
// DiffFormatWrite writes formatted diff using the Context
func DiffFormatWrite(ctx formatter.Context, changes []container.FilesystemChange) error {
render := func(format func(subContext formatter.SubContext) error) error {
for _, change := range changes {
// diffFormatWrite writes formatted diff using the [formatter.Context].
func diffFormatWrite(fmtCtx formatter.Context, changes client.ContainerDiffResult) error {
return fmtCtx.Write(newDiffContext(), func(format func(subContext formatter.SubContext) error) error {
for _, change := range changes.Changes {
if err := format(&diffContext{c: change}); err != nil {
return err
}
}
return nil
}
return ctx.Write(newDiffContext(), render)
})
}
type diffContext struct {
@ -39,12 +39,14 @@ type diffContext struct {
}
func newDiffContext() *diffContext {
diffCtx := diffContext{}
diffCtx.Header = formatter.SubHeaderContext{
"Type": changeTypeHeader,
"Path": pathHeader,
return &diffContext{
HeaderContext: formatter.HeaderContext{
Header: formatter.SubHeaderContext{
"Type": changeTypeHeader,
"Path": pathHeader,
},
},
}
return &diffCtx
}
func (d *diffContext) MarshalJSON() ([]byte, error) {

View File

@ -5,7 +5,8 @@ import (
"testing"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
)
@ -16,7 +17,7 @@ func TestDiffContextFormatWrite(t *testing.T) {
expected string
}{
{
formatter.Context{Format: NewDiffFormat("table")},
formatter.Context{Format: newDiffFormat("table")},
`CHANGE TYPE PATH
C /var/log/app.log
A /usr/app/app.js
@ -24,7 +25,7 @@ D /usr/app/old_app.js
`,
},
{
formatter.Context{Format: NewDiffFormat("table {{.Path}}")},
formatter.Context{Format: newDiffFormat("table {{.Path}}")},
`PATH
/var/log/app.log
/usr/app/app.js
@ -32,7 +33,7 @@ D /usr/app/old_app.js
`,
},
{
formatter.Context{Format: NewDiffFormat("{{.Type}}: {{.Path}}")},
formatter.Context{Format: newDiffFormat("{{.Type}}: {{.Path}}")},
`C: /var/log/app.log
A: /usr/app/app.js
D: /usr/app/old_app.js
@ -40,17 +41,19 @@ D: /usr/app/old_app.js
},
}
diffs := []container.FilesystemChange{
{Kind: container.ChangeModify, Path: "/var/log/app.log"},
{Kind: container.ChangeAdd, Path: "/usr/app/app.js"},
{Kind: container.ChangeDelete, Path: "/usr/app/old_app.js"},
diffs := client.ContainerDiffResult{
Changes: []container.FilesystemChange{
{Kind: container.ChangeModify, Path: "/var/log/app.log"},
{Kind: container.ChangeAdd, Path: "/usr/app/app.js"},
{Kind: container.ChangeDelete, Path: "/usr/app/old_app.js"},
},
}
for _, tc := range cases {
t.Run(string(tc.context.Format), func(t *testing.T) {
out := bytes.NewBufferString("")
tc.context.Output = out
err := DiffFormatWrite(tc.context, diffs)
err := diffFormatWrite(tc.context, diffs)
if err != nil {
assert.Error(t, err, tc.expected)
} else {

View File

@ -5,8 +5,7 @@ import (
"sync"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/pkg/stringid"
units "github.com/docker/go-units"
"github.com/docker/go-units"
)
const (
@ -168,6 +167,7 @@ func (c *statsContext) Container() string {
}
func (c *statsContext) Name() string {
// TODO(thaJeztah): make this explicitly trim the "/" prefix, not just any char.
if len(c.s.Name) > 1 {
return c.s.Name[1:]
}
@ -176,7 +176,7 @@ func (c *statsContext) Name() string {
func (c *statsContext) ID() string {
if c.trunc {
return stringid.TruncateID(c.s.ID)
return formatter.TruncateID(c.s.ID)
}
return c.s.ID
}

View File

@ -5,45 +5,181 @@ import (
"testing"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/pkg/stringid"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestContainerStatsContext(t *testing.T) {
containerID := stringid.GenerateRandomID()
const actorID = "c74518277ddc15a6afeaaeb06ee5f7433dcb27188224777c1efa7df1e8766d65"
var ctx statsContext
tt := []struct {
tests := []struct {
name string
stats StatsEntry
osType string
expValue string
expHeader string
call func() string
}{
{StatsEntry{Container: containerID}, "", containerID, containerHeader, ctx.Container},
{StatsEntry{CPUPercentage: 5.5}, "", "5.50%", cpuPercHeader, ctx.CPUPerc},
{StatsEntry{CPUPercentage: 5.5, IsInvalid: true}, "", "--", cpuPercHeader, ctx.CPUPerc},
{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3}, "", "0.31B / 12.3B", netIOHeader, ctx.NetIO},
{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, "", "--", netIOHeader, ctx.NetIO},
{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, "", "0.1B / 2.3B", blockIOHeader, ctx.BlockIO},
{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true}, "", "--", blockIOHeader, ctx.BlockIO},
{StatsEntry{MemoryPercentage: 10.2}, "", "10.20%", memPercHeader, ctx.MemPerc},
{StatsEntry{MemoryPercentage: 10.2, IsInvalid: true}, "", "--", memPercHeader, ctx.MemPerc},
{StatsEntry{MemoryPercentage: 10.2}, "windows", "--", memPercHeader, ctx.MemPerc},
{StatsEntry{Memory: 24, MemoryLimit: 30}, "", "24B / 30B", memUseHeader, ctx.MemUsage},
{StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, "", "-- / --", memUseHeader, ctx.MemUsage},
{StatsEntry{Memory: 24, MemoryLimit: 30}, "windows", "24B", winMemUseHeader, ctx.MemUsage},
{StatsEntry{PidsCurrent: 10}, "", "10", pidsHeader, ctx.PIDs},
{StatsEntry{PidsCurrent: 10, IsInvalid: true}, "", "--", pidsHeader, ctx.PIDs},
{StatsEntry{PidsCurrent: 10}, "windows", "--", pidsHeader, ctx.PIDs},
{
name: "Container id",
stats: StatsEntry{ID: actorID, Container: actorID},
expValue: actorID,
expHeader: containerHeader,
call: ctx.Container,
},
{
name: "Container name",
stats: StatsEntry{ID: actorID, Container: "a-long-container-name"},
expValue: "a-long-container-name",
expHeader: containerHeader,
call: ctx.Container,
},
{
name: "ID",
stats: StatsEntry{ID: actorID},
expValue: actorID,
expHeader: formatter.ContainerIDHeader,
call: ctx.ID,
},
{
name: "Name",
stats: StatsEntry{Name: "/container-name"},
expValue: "container-name",
expHeader: formatter.ContainerIDHeader,
call: ctx.Name,
},
{
name: "Name empty",
stats: StatsEntry{Name: ""},
expValue: "--",
expHeader: formatter.ContainerIDHeader,
call: ctx.Name,
},
{
name: "Name prefix only",
stats: StatsEntry{Name: "/"},
expValue: "--",
expHeader: formatter.ContainerIDHeader,
call: ctx.Name,
},
{
name: "CPUPerc",
stats: StatsEntry{CPUPercentage: 5.5},
expValue: "5.50%",
expHeader: cpuPercHeader,
call: ctx.CPUPerc,
},
{
name: "CPUPerc invalid",
stats: StatsEntry{CPUPercentage: 5.5, IsInvalid: true},
expValue: "--",
expHeader: cpuPercHeader,
call: ctx.CPUPerc,
},
{
name: "NetIO",
stats: StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3},
expValue: "0.31B / 12.3B",
expHeader: netIOHeader,
call: ctx.NetIO,
},
{
name: "NetIO invalid",
stats: StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true},
expValue: "--",
expHeader: netIOHeader,
call: ctx.NetIO,
},
{
name: "BlockIO",
stats: StatsEntry{BlockRead: 0.1, BlockWrite: 2.3},
expValue: "0.1B / 2.3B",
expHeader: blockIOHeader,
call: ctx.BlockIO,
},
{
name: "BlockIO invalid",
stats: StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true},
expValue: "--",
expHeader: blockIOHeader,
call: ctx.BlockIO,
},
{
name: "MemPerc",
stats: StatsEntry{MemoryPercentage: 10.2},
expValue: "10.20%",
expHeader: memPercHeader,
call: ctx.MemPerc,
},
{
name: "MemPerc invalid",
stats: StatsEntry{MemoryPercentage: 10.2, IsInvalid: true},
expValue: "--",
expHeader: memPercHeader,
call: ctx.MemPerc,
},
{
name: "MemPerc windows",
stats: StatsEntry{MemoryPercentage: 10.2},
osType: "windows",
expValue: "--",
expHeader: memPercHeader,
call: ctx.MemPerc,
},
{
name: "MemUsage",
stats: StatsEntry{Memory: 24, MemoryLimit: 30},
expValue: "24B / 30B",
expHeader: memUseHeader,
call: ctx.MemUsage,
},
{
name: "MemUsage invalid",
stats: StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true},
expValue: "-- / --",
expHeader: memUseHeader,
call: ctx.MemUsage,
},
{
name: "MemUsage windows",
stats: StatsEntry{Memory: 24, MemoryLimit: 30},
osType: "windows",
expValue: "24B",
expHeader: winMemUseHeader,
call: ctx.MemUsage,
},
{
name: "PIDs",
stats: StatsEntry{PidsCurrent: 10},
expValue: "10",
expHeader: pidsHeader,
call: ctx.PIDs,
},
{
name: "PIDs invalid",
stats: StatsEntry{PidsCurrent: 10, IsInvalid: true},
expValue: "--",
expHeader: pidsHeader,
call: ctx.PIDs,
},
{
name: "PIDs windows",
stats: StatsEntry{PidsCurrent: 10},
osType: "windows",
expValue: "--",
expHeader: pidsHeader,
call: ctx.PIDs,
},
}
for _, te := range tt {
ctx = statsContext{s: te.stats, os: te.osType}
if v := te.call(); v != te.expValue {
t.Fatalf("Expected %q, got %q", te.expValue, v)
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx = statsContext{s: tc.stats, os: tc.osType}
if v := tc.call(); v != tc.expValue {
t.Fatalf("Expected %q, got %q", tc.expValue, v)
}
})
}
}

View File

@ -8,9 +8,8 @@ import (
"sync"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/stdcopy"
"github.com/moby/moby/api/pkg/stdcopy"
"github.com/moby/moby/client"
"github.com/moby/term"
"github.com/sirupsen/logrus"
)
@ -19,6 +18,18 @@ import (
// TODO: This could be moved to `pkg/term`.
var defaultEscapeKeys = []byte{16, 17}
// readCloserWrapper wraps an io.Reader, and implements an io.ReadCloser
// It calls the given callback function when closed.
type readCloserWrapper struct {
io.Reader
closer func() error
}
// Close calls back the passed closer function
func (r *readCloserWrapper) Close() error {
return r.closer()
}
// A hijackedIOStreamer handles copying input to and output from streams to the
// connection.
type hijackedIOStreamer struct {
@ -27,7 +38,7 @@ type hijackedIOStreamer struct {
outputStream io.Writer
errorStream io.Writer
resp types.HijackedResponse
resp client.HijackedResponse
tty bool
detachKeys string
@ -100,7 +111,10 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
}
}
h.inputStream = ioutils.NewReadCloserWrapper(term.NewEscapeProxy(h.inputStream, escapeKeys), h.inputStream.Close)
h.inputStream = &readCloserWrapper{
Reader: term.NewEscapeProxy(h.inputStream, escapeKeys),
closer: h.inputStream.Close,
}
return restore, nil
}

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package container
@ -11,6 +11,7 @@ import (
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/inspect"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -21,7 +22,7 @@ type inspectOptions struct {
}
// newInspectCommand creates a new cobra.Command for `docker container inspect`
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
func newInspectCommand(dockerCLI command.Cli) *cobra.Command {
var opts inspectOptions
cmd := &cobra.Command{
@ -30,9 +31,10 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.refs = args
return runInspect(cmd.Context(), dockerCli, opts)
return runInspect(cmd.Context(), dockerCLI, opts)
},
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
ValidArgsFunction: completion.ContainerNames(dockerCLI, true),
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -45,6 +47,10 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error {
apiClient := dockerCLI.Client()
return inspect.Inspect(dockerCLI.Out(), opts.refs, opts.format, func(ref string) (any, []byte, error) {
return apiClient.ContainerInspectWithRaw(ctx, ref, opts.size)
res, err := apiClient.ContainerInspect(ctx, ref, client.ContainerInspectOptions{Size: opts.size})
if err != nil {
return nil, nil, err
}
return &res.Container, res.Raw, nil
})
}

View File

@ -8,6 +8,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -17,8 +18,8 @@ type killOptions struct {
containers []string
}
// NewKillCommand creates a new cobra.Command for `docker kill`
func NewKillCommand(dockerCli command.Cli) *cobra.Command {
// newKillCommand creates a new cobra.Command for "docker container kill"
func newKillCommand(dockerCLI command.Cli) *cobra.Command {
var opts killOptions
cmd := &cobra.Command{
@ -27,12 +28,13 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args
return runKill(cmd.Context(), dockerCli, &opts)
return runKill(cmd.Context(), dockerCLI, &opts)
},
Annotations: map[string]string{
"aliases": "docker container kill, docker kill",
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -46,7 +48,10 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command {
func runKill(ctx context.Context, dockerCLI command.Cli, opts *killOptions) error {
apiClient := dockerCLI.Client()
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, container string) error {
return apiClient.ContainerKill(ctx, container, opts.signal)
_, err := apiClient.ContainerKill(ctx, container, client.ContainerKillOptions{
Signal: opts.signal,
})
return err
})
var errs []error

View File

@ -8,23 +8,20 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestRunKill(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerKillFunc: func(
ctx context.Context,
container string,
signal string,
) error {
assert.Assert(t, is.Equal(signal, "STOP"))
return nil
containerKillFunc: func(ctx context.Context, container string, options client.ContainerKillOptions) (client.ContainerKillResult, error) {
assert.Assert(t, is.Equal(options.Signal, "STOP"))
return client.ContainerKillResult{}, nil
},
})
cmd := NewKillCommand(cli)
cmd := newKillCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs([]string{
@ -47,16 +44,12 @@ func TestRunKill(t *testing.T) {
func TestRunKillClientError(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerKillFunc: func(
ctx context.Context,
container string,
signal string,
) error {
return fmt.Errorf("client error for container %s", container)
containerKillFunc: func(ctx context.Context, container string, options client.ContainerKillOptions) (client.ContainerKillResult, error) {
return client.ContainerKillResult{}, fmt.Errorf("client error for container %s", container)
},
})
cmd := NewKillCommand(cli)
cmd := newKillCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)

View File

@ -2,17 +2,16 @@ package container
import (
"context"
"fmt"
"io"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
"github.com/docker/cli/templates"
"github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -28,8 +27,8 @@ type psOptions struct {
filter opts.FilterOpt
}
// NewPsCommand creates a new cobra.Command for `docker ps`
func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
// newPsCommand creates a new cobra.Command for "docker container ps"
func newPsCommand(dockerCLI command.Cli) *cobra.Command {
options := psOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
@ -44,7 +43,8 @@ func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
"category-top": "3",
"aliases": "docker container ls, docker container list, docker container ps, docker ps",
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -62,14 +62,14 @@ func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
}
func newListCommand(dockerCLI command.Cli) *cobra.Command {
cmd := *NewPsCommand(dockerCLI)
cmd := *newPsCommand(dockerCLI)
cmd.Aliases = []string{"ps", "list"}
cmd.Use = "ls [OPTIONS]"
return &cmd
}
func buildContainerListOptions(options *psOptions) (*container.ListOptions, error) {
listOptions := &container.ListOptions{
func buildContainerListOptions(options *psOptions) (client.ContainerListOptions, error) {
listOptions := client.ContainerListOptions{
All: options.all,
Limit: options.last,
Size: options.size,
@ -82,9 +82,9 @@ func buildContainerListOptions(options *psOptions) (*container.ListOptions, erro
// always validate template when `--format` is used, for consistency
if len(options.format) > 0 {
tmpl, err := templates.NewParse("", options.format)
tmpl, err := templates.Parse(options.format)
if err != nil {
return nil, errors.Wrap(err, "failed to parse template")
return client.ContainerListOptions{}, fmt.Errorf("failed to parse template: %w", err)
}
optionsProcessor := formatter.NewContainerContext()
@ -92,7 +92,7 @@ func buildContainerListOptions(options *psOptions) (*container.ListOptions, erro
// This shouldn't error out but swallowing the error makes it harder
// to track down if preProcessor issues come up.
if err := tmpl.Execute(io.Discard, optionsProcessor); err != nil {
return nil, errors.Wrap(err, "failed to execute template")
return client.ContainerListOptions{}, fmt.Errorf("failed to execute template: %w", err)
}
// if `size` was not explicitly set to false (with `--size=false`)
@ -127,7 +127,7 @@ func runPs(ctx context.Context, dockerCLI command.Cli, options *psOptions) error
return err
}
containers, err := dockerCLI.Client().ContainerList(ctx, *listOptions)
res, err := dockerCLI.Client().ContainerList(ctx, listOptions)
if err != nil {
return err
}
@ -137,5 +137,5 @@ func runPs(ctx context.Context, dockerCLI command.Cli, options *psOptions) error
Format: formatter.NewContainerFormat(options.format, options.quiet, listOptions.Size),
Trunc: !options.noTrunc,
}
return formatter.ContainerWrite(containerCtx, containers)
return formatter.ContainerWrite(containerCtx, res.Items)
}

View File

@ -9,7 +9,8 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
@ -25,7 +26,7 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
expectedAll bool
expectedSize bool
expectedLimit int
expectedFilters map[string]string
expectedFilters client.Filters
}{
{
psOpts: &psOptions{
@ -34,13 +35,10 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
last: 5,
filter: filters,
},
expectedAll: true,
expectedSize: true,
expectedLimit: 5,
expectedFilters: map[string]string{
"foo": "bar",
"baz": "foo",
},
expectedAll: true,
expectedSize: true,
expectedLimit: 5,
expectedFilters: make(client.Filters).Add("foo", "bar").Add("baz", "foo"),
},
{
psOpts: &psOptions{
@ -49,10 +47,9 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
last: -1,
nLatest: true,
},
expectedAll: true,
expectedSize: true,
expectedLimit: 1,
expectedFilters: make(map[string]string),
expectedAll: true,
expectedSize: true,
expectedLimit: 1,
},
{
psOpts: &psOptions{
@ -63,13 +60,10 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
// With .Size, size should be true
format: "{{.Size}}",
},
expectedAll: true,
expectedSize: true,
expectedLimit: 5,
expectedFilters: map[string]string{
"foo": "bar",
"baz": "foo",
},
expectedAll: true,
expectedSize: true,
expectedLimit: 5,
expectedFilters: make(client.Filters).Add("foo", "bar").Add("baz", "foo"),
},
{
psOpts: &psOptions{
@ -80,13 +74,10 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
// With .Size, size should be true
format: "{{.Size}} {{.CreatedAt}} {{upper .Networks}}",
},
expectedAll: true,
expectedSize: true,
expectedLimit: 5,
expectedFilters: map[string]string{
"foo": "bar",
"baz": "foo",
},
expectedAll: true,
expectedSize: true,
expectedLimit: 5,
expectedFilters: make(client.Filters).Add("foo", "bar").Add("baz", "foo"),
},
{
psOpts: &psOptions{
@ -97,13 +88,10 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
// Without .Size, size should be false
format: "{{.CreatedAt}} {{.Networks}}",
},
expectedAll: true,
expectedSize: false,
expectedLimit: 5,
expectedFilters: map[string]string{
"foo": "bar",
"baz": "foo",
},
expectedAll: true,
expectedSize: false,
expectedLimit: 5,
expectedFilters: make(client.Filters).Add("foo", "bar").Add("baz", "foo"),
},
}
@ -114,21 +102,14 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
assert.Check(t, is.Equal(c.expectedAll, options.All))
assert.Check(t, is.Equal(c.expectedSize, options.Size))
assert.Check(t, is.Equal(c.expectedLimit, options.Limit))
assert.Check(t, is.Equal(len(c.expectedFilters), options.Filters.Len()))
for k, v := range c.expectedFilters {
f := options.Filters
if !f.ExactMatch(k, v) {
t.Fatalf("Expected filter with key %s to be %s but got %s", k, v, f.Get(k))
}
}
assert.Check(t, is.DeepEqual(c.expectedFilters, options.Filters))
}
}
func TestContainerListErrors(t *testing.T) {
testCases := []struct {
flags map[string]string
containerListFunc func(container.ListOptions) ([]container.Summary, error)
containerListFunc func(client.ContainerListOptions) (client.ContainerListResult, error)
expectedError string
}{
{
@ -144,8 +125,8 @@ func TestContainerListErrors(t *testing.T) {
expectedError: `wrong number of args for join`,
},
{
containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) {
return nil, errors.New("error listing containers")
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{}, errors.New("error listing containers")
},
expectedError: "error listing containers",
},
@ -168,13 +149,15 @@ func TestContainerListErrors(t *testing.T) {
func TestContainerListWithoutFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo")),
*builders.Container("c3", builders.WithPort(80, 80, builders.TCP), builders.WithPort(81, 81, builders.TCP), builders.WithPort(82, 82, builders.TCP)),
*builders.Container("c4", builders.WithPort(81, 81, builders.UDP)),
*builders.Container("c5", builders.WithPort(82, 82, builders.IP("8.8.8.8"), builders.TCP)),
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo")),
*builders.Container("c3", builders.WithPort(80, 80, builders.TCP), builders.WithPort(81, 81, builders.TCP), builders.WithPort(82, 82, builders.TCP)),
*builders.Container("c4", builders.WithPort(81, 81, builders.UDP)),
*builders.Container("c5", builders.WithPort(82, 82, builders.IP("8.8.8.8"), builders.TCP)),
},
}, nil
},
})
@ -188,10 +171,12 @@ func TestContainerListWithoutFormat(t *testing.T) {
func TestContainerListNoTrunc(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo/bar")),
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo/bar")),
},
}, nil
},
})
@ -207,10 +192,12 @@ func TestContainerListNoTrunc(t *testing.T) {
// Test for GitHub issue docker/docker#21772
func TestContainerListNamesMultipleTime(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo/bar")),
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo/bar")),
},
}, nil
},
})
@ -226,10 +213,12 @@ func TestContainerListNamesMultipleTime(t *testing.T) {
// Test for GitHub issue docker/docker#30291
func TestContainerListFormatTemplateWithArg(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1", builders.WithLabel("some.label", "value")),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")),
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1", builders.WithLabel("some.label", "value")),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")),
},
}, nil
},
})
@ -279,9 +268,9 @@ func TestContainerListFormatSizeSetsOption(t *testing.T) {
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(options container.ListOptions) ([]container.Summary, error) {
containerListFunc: func(options client.ContainerListOptions) (client.ContainerListResult, error) {
assert.Check(t, is.Equal(options.Size, tc.sizeExpected))
return []container.Summary{}, nil
return client.ContainerListResult{}, nil
},
})
cmd := newListCommand(cli)
@ -299,10 +288,12 @@ func TestContainerListFormatSizeSetsOption(t *testing.T) {
func TestContainerListWithConfigFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1", builders.WithLabel("some.label", "value"), builders.WithSize(10700000)),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar"), builders.WithSize(3200000)),
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1", builders.WithLabel("some.label", "value"), builders.WithSize(10700000)),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar"), builders.WithSize(3200000)),
},
}, nil
},
})
@ -319,10 +310,12 @@ func TestContainerListWithConfigFormat(t *testing.T) {
func TestContainerListWithFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1", builders.WithLabel("some.label", "value")),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")),
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1", builders.WithLabel("some.label", "value")),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")),
},
}, nil
},
})

View File

@ -7,8 +7,8 @@ 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/container"
"github.com/docker/docker/pkg/stdcopy"
"github.com/moby/moby/api/pkg/stdcopy"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -23,8 +23,8 @@ type logsOptions struct {
container string
}
// NewLogsCommand creates a new cobra.Command for `docker logs`
func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
// newLogsCommand creates a new cobra.Command for "docker container logs"
func newLogsCommand(dockerCLI command.Cli) *cobra.Command {
var opts logsOptions
cmd := &cobra.Command{
@ -33,12 +33,13 @@ func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.container = args[0]
return runLogs(cmd.Context(), dockerCli, &opts)
return runLogs(cmd.Context(), dockerCLI, &opts)
},
Annotations: map[string]string{
"aliases": "docker container logs, docker logs",
},
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
ValidArgsFunction: completion.ContainerNames(dockerCLI, true),
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -53,12 +54,12 @@ func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
}
func runLogs(ctx context.Context, dockerCli command.Cli, opts *logsOptions) error {
c, err := dockerCli.Client().ContainerInspect(ctx, opts.container)
c, err := dockerCli.Client().ContainerInspect(ctx, opts.container, client.ContainerInspectOptions{})
if err != nil {
return err
}
responseBody, err := dockerCli.Client().ContainerLogs(ctx, c.ID, container.LogsOptions{
resp, err := dockerCli.Client().ContainerLogs(ctx, c.Container.ID, client.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Since: opts.since,
@ -71,12 +72,12 @@ func runLogs(ctx context.Context, dockerCli command.Cli, opts *logsOptions) erro
if err != nil {
return err
}
defer responseBody.Close()
defer func() { _ = resp.Close() }()
if c.Config.Tty {
_, err = io.Copy(dockerCli.Out(), responseBody)
if c.Container.Config.Tty {
_, err = io.Copy(dockerCli.Out(), resp)
} else {
_, err = stdcopy.StdCopy(dockerCli.Out(), dockerCli.Err(), responseBody)
_, err = stdcopy.StdCopy(dockerCli.Out(), dockerCli.Err(), resp)
}
return err
}

View File

@ -2,27 +2,22 @@ package container
import (
"context"
"io"
"strings"
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
var logFn = func(expectedOut string) func(string, container.LogsOptions) (io.ReadCloser, error) {
return func(container string, opts container.LogsOptions) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader(expectedOut)), nil
}
}
func TestRunLogs(t *testing.T) {
inspectFn := func(containerID string) (container.InspectResponse, error) {
return container.InspectResponse{
Config: &container.Config{Tty: true},
ContainerJSONBase: &container.ContainerJSONBase{State: &container.State{Running: false}},
inspectFn := func(containerID string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{
Container: container.InspectResponse{
Config: &container.Config{Tty: true},
State: &container.State{Running: false},
},
}, nil
}
@ -38,7 +33,13 @@ func TestRunLogs(t *testing.T) {
doc: "successful logs",
expectedOut: "foo",
options: &logsOptions{},
client: &fakeClient{logFunc: logFn("foo"), inspectFunc: inspectFn},
client: &fakeClient{
logFunc: func(container string, opts client.ContainerLogsOptions) (client.ContainerLogsResult, error) {
// FIXME(thaJeztah): how to mock this?
return mockContainerLogsResult("foo"), nil
},
inspectFunc: inspectFn,
},
},
}

View File

@ -1,26 +1,30 @@
// FIXME(vvoland): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package container
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net"
"net/netip"
"os"
"path"
"path/filepath"
"reflect"
"strconv"
"slices"
"strings"
"time"
"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/internal/lazyregexp"
"github.com/docker/cli/internal/volumespec"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
mounttypes "github.com/docker/docker/api/types/mount"
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/moby/moby/api/types/container"
"github.com/moby/moby/api/types/mount"
"github.com/moby/moby/api/types/network"
"github.com/spf13/pflag"
cdi "tags.cncf.io/container-device-interface/pkg/parser"
)
@ -52,7 +56,7 @@ type containerOptions struct {
deviceWriteBps opts.ThrottledeviceOpt
links opts.ListOpts
aliases opts.ListOpts
linkLocalIPs opts.ListOpts
linkLocalIPs opts.ListOpts // TODO(thaJeztah): we need a flag-type to handle []netip.Addr directly
deviceReadIOps opts.ThrottledeviceOpt
deviceWriteIOps opts.ThrottledeviceOpt
env opts.ListOpts
@ -64,7 +68,7 @@ type containerOptions struct {
sysctls *opts.MapOpts
publish opts.ListOpts
expose opts.ListOpts
dns opts.ListOpts
dns opts.ListOpts // TODO(thaJeztah): we need a flag-type to handle []netip.Addr directly
dnsSearch opts.ListOpts
dnsOptions opts.ListOpts
extraHosts opts.ListOpts
@ -94,7 +98,6 @@ type containerOptions struct {
memory opts.MemBytes
memoryReservation opts.MemBytes
memorySwap opts.MemSwapBytes
kernelMemory opts.MemBytes
user string
workingDir string
cpuCount int64
@ -113,8 +116,8 @@ type containerOptions struct {
swappiness int64
netMode opts.NetworkOpt
macAddress string
ipv4Address string
ipv6Address string
ipv4Address net.IP // TODO(thaJeztah): we need a flag-type to handle netip.Addr directly
ipv6Address net.IP // TODO(thaJeztah): we need a flag-type to handle netip.Addr directly
ipcMode string
pidsLimit int64
restartPolicy string
@ -230,8 +233,8 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
flags.MarkHidden("dns-opt")
flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains")
flags.Var(&copts.expose, "expose", "Expose a port or a range of ports")
flags.StringVar(&copts.ipv4Address, "ip", "", "IPv4 address (e.g., 172.30.100.104)")
flags.StringVar(&copts.ipv6Address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)")
flags.IPVar(&copts.ipv4Address, "ip", nil, "IPv4 address (e.g., 172.30.100.104)")
flags.IPVar(&copts.ipv6Address, "ip6", nil, "IPv6 address (e.g., 2001:db8::33)")
flags.Var(&copts.links, "link", "Add link to another container")
flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses")
flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g., 92:d0:c6:0a:29:33)")
@ -294,7 +297,6 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
flags.SetAnnotation("io-maxbandwidth", "ostype", []string{"windows"})
flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)")
flags.SetAnnotation("io-maxiops", "ostype", []string{"windows"})
flags.Var(&copts.kernelMemory, "kernel-memory", "Kernel memory limit")
flags.VarP(&copts.memory, "memory", "m", "Memory limit")
flags.Var(&copts.memoryReservation, "memory-reservation", "Memory soft limit")
flags.Var(&copts.memorySwap, "memory-swap", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
@ -318,13 +320,18 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
flags.Var(copts.annotations, "annotation", "Add an annotation to the container (passed through to the OCI runtime)")
flags.SetAnnotation("annotation", "version", []string{"1.43"})
// TODO(thaJeztah): remove in next release (v30.0, or v29.x)
var stub opts.MemBytes
flags.Var(&stub, "kernel-memory", "Kernel memory limit (deprecated)")
_ = flags.MarkDeprecated("kernel-memory", "and no longer supported by the kernel")
return copts
}
type containerConfig struct {
Config *container.Config
HostConfig *container.HostConfig
NetworkingConfig *networktypes.NetworkingConfig
NetworkingConfig *network.NetworkingConfig
}
// parse parses the args for the specified command and generates a Config,
@ -341,8 +348,8 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
// Validate the input mac address
if copts.macAddress != "" {
if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil {
return nil, errors.Errorf("%s is not a valid mac address", copts.macAddress)
if _, err := net.ParseMAC(strings.TrimSpace(copts.macAddress)); err != nil {
return nil, fmt.Errorf("%s is not a valid mac address", copts.macAddress)
}
}
if copts.stdin {
@ -358,14 +365,14 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
swappiness := copts.swappiness
if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
return nil, errors.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
return nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
}
var binds []string
volumes := copts.volumes.GetMap()
// add any bind targets to the list of container volumes
for bind := range copts.volumes.GetMap() {
parsed, err := loader.ParseVolume(bind)
parsed, err := volumespec.Parse(bind)
if err != nil {
return nil, err
}
@ -373,7 +380,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
if parsed.Source != "" {
toBind := bind
if parsed.Type == string(mounttypes.TypeBind) {
if parsed.Type == string(mount.TypeBind) {
if hostPart, targetPath, ok := strings.Cut(bind, ":"); ok {
if !filepath.IsAbs(hostPart) && strings.HasPrefix(hostPart, ".") {
if absHostPart, err := filepath.Abs(hostPart); err == nil {
@ -400,61 +407,73 @@ 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{""}
}
publishOpts := copts.publish.GetSlice()
var (
ports map[nat.Port]struct{}
portBindings map[nat.Port][]nat.PortBinding
convertedOpts []string
)
convertedOpts, err = convertToStandardNotation(publishOpts)
// TODO(thaJeztah): remove uses of go-connections/nat here.
convertedOpts, err := convertToStandardNotation(copts.publish.GetSlice())
if err != nil {
return nil, err
}
ports, portBindings, err = nat.ParsePortSpecs(convertedOpts)
ports, natPortBindings, err := nat.ParsePortSpecs(convertedOpts)
if err != nil {
return nil, err
}
portBindings := network.PortMap{}
for port, bindings := range natPortBindings {
p, err := network.ParsePort(string(port))
if err != nil {
return nil, err
}
portBindings[p] = []network.PortBinding{}
for _, b := range bindings {
var hostIP netip.Addr
if b.HostIP != "" {
hostIP, err = netip.ParseAddr(b.HostIP)
if err != nil {
return nil, err
}
}
portBindings[p] = append(portBindings[p], network.PortBinding{
HostIP: hostIP,
HostPort: b.HostPort,
})
}
}
// Add published ports as exposed ports.
exposedPorts := network.PortSet{}
for port := range ports {
p, err := network.ParsePort(string(port))
if err != nil {
return nil, err
}
exposedPorts[p] = struct{}{}
}
// Merge in exposed ports to the map of published ports
for _, e := range copts.expose.GetSlice() {
if strings.Contains(e, ":") {
return nil, errors.Errorf("invalid port format for --expose: %s", e)
}
// support two formats for expose, original format <portnum>/[<proto>]
// or <startport-endport>/[<proto>]
proto, port := nat.SplitProtoPort(e)
pr, err := network.ParsePortRange(e)
if err != nil {
return nil, fmt.Errorf("invalid range format for --expose: %w", err)
}
// parse the start and end port and create a sequence of ports to expose
// if expose a port, the start and end port are the same
start, end, err := nat.ParsePortRange(port)
if err != nil {
return nil, errors.Errorf("invalid range format for --expose: %s, error: %s", e, err)
}
for i := start; i <= end; i++ {
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
if err != nil {
return nil, err
}
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
for p := range pr.All() {
exposedPorts[p] = struct{}{}
}
}
@ -462,23 +481,19 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
// device path (as opposed to during flag parsing), as at the time we are
// parsing flags, we haven't yet sent a _ping to the daemon to determine
// what operating system it is.
deviceMappings := []container.DeviceMapping{}
var cdiDeviceNames []string
for _, device := range copts.devices.GetSlice() {
var (
validated string
deviceMapping container.DeviceMapping
err error
)
devices := copts.devices.GetSlice()
deviceMappings := make([]container.DeviceMapping, 0, len(devices))
cdiDeviceNames := make([]string, 0, len(devices))
for _, device := range devices {
if cdi.IsQualifiedName(device) {
cdiDeviceNames = append(cdiDeviceNames, device)
continue
}
validated, err = validateDevice(device, serverOS)
validated, err := validateDevice(device, serverOS)
if err != nil {
return nil, err
}
deviceMapping, err = parseDevice(validated, serverOS)
deviceMapping, err := parseDevice(validated, serverOS)
if err != nil {
return nil, err
}
@ -499,22 +514,22 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
pidMode := container.PidMode(copts.pidMode)
if !pidMode.Valid() {
return nil, errors.Errorf("--pid: invalid PID mode")
return nil, errors.New("--pid: invalid PID mode")
}
utsMode := container.UTSMode(copts.utsMode)
if !utsMode.Valid() {
return nil, errors.Errorf("--uts: invalid UTS mode")
return nil, errors.New("--uts: invalid UTS mode")
}
usernsMode := container.UsernsMode(copts.usernsMode)
if !usernsMode.Valid() {
return nil, errors.Errorf("--userns: invalid USER mode")
return nil, errors.New("--userns: invalid USER mode")
}
cgroupnsMode := container.CgroupnsMode(copts.cgroupnsMode)
if !cgroupnsMode.Valid() {
return nil, errors.Errorf("--cgroupns: invalid CGROUP mode")
return nil, errors.New("--cgroupns: invalid CGROUP mode")
}
restartPolicy, err := opts.ParseRestartPolicy(copts.restartPolicy)
@ -549,22 +564,22 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
copts.healthStartInterval != 0
if copts.noHealthcheck {
if haveHealthSettings {
return nil, errors.Errorf("--no-healthcheck conflicts with --health-* options")
return nil, errors.New("--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}
}
if copts.healthInterval < 0 {
return nil, errors.Errorf("--health-interval cannot be negative")
return nil, errors.New("--health-interval cannot be negative")
}
if copts.healthTimeout < 0 {
return nil, errors.Errorf("--health-timeout cannot be negative")
return nil, errors.New("--health-timeout cannot be negative")
}
if copts.healthRetries < 0 {
return nil, errors.Errorf("--health-retries cannot be negative")
return nil, errors.New("--health-retries cannot be negative")
}
if copts.healthStartPeriod < 0 {
return nil, errors.New("--health-start-period cannot be negative")
@ -598,7 +613,6 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
MemoryReservation: copts.memoryReservation.Value(),
MemorySwap: copts.memorySwap.Value(),
MemorySwappiness: &copts.swappiness,
KernelMemory: copts.kernelMemory.Value(),
OomKillDisable: &copts.oomKillDisable,
NanoCPUs: copts.cpus.Value(),
CPUCount: copts.cpuCount,
@ -628,7 +642,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
config := &container.Config{
Hostname: copts.hostname,
Domainname: copts.domainname,
ExposedPorts: ports,
ExposedPorts: exposedPorts,
User: copts.user,
Tty: copts.tty,
OpenStdin: copts.stdin,
@ -639,7 +653,6 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
Cmd: runCmd,
Image: copts.Image,
Volumes: volumes,
MacAddress: copts.macAddress,
Entrypoint: entrypoint,
WorkingDir: copts.workingDir,
Labels: opts.ConvertKVStringsToMap(labels),
@ -664,7 +677,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
// but pre created containers can still have those nil values.
// See https://github.com/docker/docker/pull/17779
// for a more detailed explanation on why we don't want that.
DNS: copts.dns.GetAllOrEmpty(),
DNS: toNetipAddrSlice(copts.dns.GetAllOrEmpty()),
DNSSearch: copts.dnsSearch.GetAllOrEmpty(),
DNSOptions: copts.dnsOptions.GetAllOrEmpty(),
ExtraHosts: copts.extraHosts.GetSlice(),
@ -675,8 +688,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,
@ -697,7 +710,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
}
if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() {
return nil, errors.Errorf("conflicting options: cannot specify both --restart and --rm")
return nil, errors.New("conflicting options: cannot specify both --restart and --rm")
}
// only set this value if the user provided the flag, else it should default to nil
@ -710,25 +723,17 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
config.StdinOnce = true
}
networkingConfig := &networktypes.NetworkingConfig{
EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
}
networkingConfig.EndpointsConfig, err = parseNetworkOpts(copts)
epCfg, err := parseNetworkOpts(copts)
if err != nil {
return nil, err
}
// Put the endpoint-specific MacAddress of the "main" network attachment into the container Config for backward
// compatibility with older daemons.
if nw, ok := networkingConfig.EndpointsConfig[hostConfig.NetworkMode.NetworkName()]; ok {
config.MacAddress = nw.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
}
return &containerConfig{
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
Config: config,
HostConfig: hostConfig,
NetworkingConfig: &network.NetworkingConfig{
EndpointsConfig: epCfg,
},
}, nil
}
@ -739,9 +744,9 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
// this function may return _multiple_ endpoints, which is not currently supported
// by the daemon, but may be in future; it's up to the daemon to produce an error
// in case that is not supported.
func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.EndpointSettings, error) {
func parseNetworkOpts(copts *containerOptions) (map[string]*network.EndpointSettings, error) {
var (
endpoints = make(map[string]*networktypes.EndpointSettings, len(copts.netMode.Value()))
endpoints = make(map[string]*network.EndpointSettings, len(copts.netMode.Value()))
hasUserDefined, hasNonUserDefined bool
)
@ -780,14 +785,14 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin
return nil, err
}
if _, ok := endpoints[n.Target]; ok {
return nil, invalidParameter(errors.Errorf("network %q is specified multiple times", n.Target))
return nil, invalidParameter(fmt.Errorf("network %q is specified multiple times", n.Target))
}
// For backward compatibility: if no custom options are provided for the network,
// and only a single network is specified, omit the endpoint-configuration
// on the client (the daemon will still create it when creating the container)
if i == 0 && len(copts.netMode.Value()) == 1 {
if ep == nil || reflect.DeepEqual(*ep, networktypes.EndpointSettings{}) {
if ep == nil || reflect.DeepEqual(*ep, network.EndpointSettings{}) {
continue
}
}
@ -807,10 +812,10 @@ func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOption
if len(n.Links) > 0 && copts.links.Len() > 0 {
return invalidParameter(errors.New("conflicting options: cannot specify both --link and per-network links"))
}
if n.IPv4Address != "" && copts.ipv4Address != "" {
if n.IPv4Address.IsValid() && copts.ipv4Address != nil {
return invalidParameter(errors.New("conflicting options: cannot specify both --ip and per-network IPv4 address"))
}
if n.IPv6Address != "" && copts.ipv6Address != "" {
if n.IPv6Address.IsValid() && copts.ipv6Address != nil {
return invalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address"))
}
if n.MacAddress != "" && copts.macAddress != "" {
@ -829,23 +834,26 @@ func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOption
n.Links = make([]string, copts.links.Len())
copy(n.Links, copts.links.GetSlice())
}
if copts.ipv4Address != "" {
n.IPv4Address = copts.ipv4Address
if copts.ipv4Address != nil {
if ipv4, ok := netip.AddrFromSlice(copts.ipv4Address.To4()); ok {
n.IPv4Address = ipv4
}
}
if copts.ipv6Address != "" {
n.IPv6Address = copts.ipv6Address
if copts.ipv6Address != nil {
if ipv6, ok := netip.AddrFromSlice(copts.ipv6Address.To16()); ok {
n.IPv6Address = ipv6
}
}
if copts.macAddress != "" {
n.MacAddress = copts.macAddress
}
if copts.linkLocalIPs.Len() > 0 {
n.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len())
copy(n.LinkLocalIPs, copts.linkLocalIPs.GetSlice())
n.LinkLocalIPs = toNetipAddrSlice(copts.linkLocalIPs.GetSlice())
}
return nil
}
func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.EndpointSettings, error) {
func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*network.EndpointSettings, error) {
if strings.TrimSpace(ep.Target) == "" {
return nil, errors.New("no name set for network")
}
@ -858,7 +866,7 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End
}
}
epConfig := &networktypes.EndpointSettings{
epConfig := &network.EndpointSettings{
GwPriority: ep.GwPriority,
}
epConfig.Aliases = append(epConfig.Aliases, ep.Aliases...)
@ -869,18 +877,19 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End
if len(ep.Links) > 0 {
epConfig.Links = ep.Links
}
if ep.IPv4Address != "" || ep.IPv6Address != "" || len(ep.LinkLocalIPs) > 0 {
epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{
if ep.IPv4Address.IsValid() || ep.IPv6Address.IsValid() || len(ep.LinkLocalIPs) > 0 {
epConfig.IPAMConfig = &network.EndpointIPAMConfig{
IPv4Address: ep.IPv4Address,
IPv6Address: ep.IPv6Address,
LinkLocalIPs: ep.LinkLocalIPs,
}
}
if ep.MacAddress != "" {
if _, err := opts.ValidateMACAddress(ep.MacAddress); err != nil {
return nil, errors.Errorf("%s is not a valid mac address", ep.MacAddress)
ma, err := net.ParseMAC(strings.TrimSpace(ep.MacAddress))
if err != nil {
return nil, fmt.Errorf("%s is not a valid mac address", ep.MacAddress)
}
epConfig.MacAddress = ep.MacAddress
epConfig.MacAddress = network.HardwareAddr(ma)
}
return epConfig, nil
}
@ -893,7 +902,7 @@ func convertToStandardNotation(ports []string) ([]string, error) {
for _, param := range strings.Split(publish, ",") {
k, v, ok := strings.Cut(param, "=")
if !ok || k == "" {
return optsList, errors.Errorf("invalid publish opts format (should be name=value but got '%s')", param)
return optsList, fmt.Errorf("invalid publish opts format (should be name=value but got '%s')", param)
}
params[k] = v
}
@ -908,7 +917,7 @@ func convertToStandardNotation(ports []string) ([]string, error) {
func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
loggingOptsMap := opts.ConvertKVStringsToMap(loggingOpts)
if loggingDriver == "none" && len(loggingOpts) > 0 {
return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", loggingDriver)
return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver)
}
return loggingOptsMap, nil
}
@ -922,7 +931,7 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) {
}
if (!ok || v == "") && k != "no-new-privileges" {
// "no-new-privileges" is the only option that does not require a value.
return securityOpts, errors.Errorf("Invalid --security-opt: %q", opt)
return securityOpts, fmt.Errorf("invalid --security-opt: %q", opt)
}
if k == "seccomp" {
switch v {
@ -933,11 +942,11 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) {
// content if it's valid JSON.
f, err := os.ReadFile(v)
if err != nil {
return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", v, err)
return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %w", v, err)
}
b := bytes.NewBuffer(nil)
if err := json.Compact(b, f); err != nil {
return securityOpts, errors.Errorf("compacting json for seccomp profile (%s) failed: %v", v, err)
return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %w", v, err)
}
securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
}
@ -972,7 +981,7 @@ func parseStorageOpts(storageOpts []string) (map[string]string, error) {
for _, option := range storageOpts {
k, v, ok := strings.Cut(option, "=")
if !ok {
return nil, errors.Errorf("invalid storage option")
return nil, errors.New("invalid storage option")
}
m[k] = v
}
@ -987,7 +996,7 @@ func parseDevice(device, serverOS string) (container.DeviceMapping, error) {
case "windows":
return parseWindowsDevice(device)
}
return container.DeviceMapping{}, errors.Errorf("unknown server OS: %s", serverOS)
return container.DeviceMapping{}, fmt.Errorf("unknown server OS: %s", serverOS)
}
// parseLinuxDevice parses a device mapping string to a container.DeviceMapping struct
@ -1011,7 +1020,7 @@ func parseLinuxDevice(device string) (container.DeviceMapping, error) {
case 1:
src = arr[0]
default:
return container.DeviceMapping{}, errors.Errorf("invalid device specification: %s", device)
return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device)
}
if dst == "" {
@ -1041,7 +1050,7 @@ func validateDeviceCgroupRule(val string) (string, error) {
return val, nil
}
return val, errors.Errorf("invalid device cgroup format '%s'", val)
return val, fmt.Errorf("invalid device cgroup format '%s'", val)
}
// validDeviceMode checks if the mode for device is valid or not.
@ -1073,7 +1082,7 @@ func validateDevice(val string, serverOS string) (string, error) {
// Windows does validation entirely server-side
return val, nil
}
return "", errors.Errorf("unknown server OS: %s", serverOS)
return "", fmt.Errorf("unknown server OS: %s", serverOS)
}
// validateLinuxPath is the implementation of validateDevice knowing that the
@ -1088,12 +1097,12 @@ func validateLinuxPath(val string, validator func(string) bool) (string, error)
var mode string
if strings.Count(val, ":") > 2 {
return val, errors.Errorf("bad format for path: %s", val)
return val, fmt.Errorf("bad format for path: %s", val)
}
split := strings.SplitN(val, ":", 3)
if split[0] == "" {
return val, errors.Errorf("bad format for path: %s", val)
return val, fmt.Errorf("bad format for path: %s", val)
}
switch len(split) {
case 1:
@ -1112,13 +1121,13 @@ func validateLinuxPath(val string, validator func(string) bool) (string, error)
containerPath = split[1]
mode = split[2]
if isValid := validator(split[2]); !isValid {
return val, errors.Errorf("bad mode specified: %s", mode)
return val, fmt.Errorf("bad mode specified: %s", mode)
}
val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
}
if !path.IsAbs(containerPath) {
return val, errors.Errorf("%s is not an absolute path", containerPath)
return val, fmt.Errorf("%s is not an absolute path", containerPath)
}
return val, nil
}
@ -1126,10 +1135,23 @@ func validateLinuxPath(val string, validator func(string) bool) (string, error)
// validateAttach validates that the specified string is a valid attach option.
func validateAttach(val string) (string, error) {
s := strings.ToLower(val)
for _, str := range []string{"stdin", "stdout", "stderr"} {
if s == str {
return s, nil
}
if slices.Contains([]string{"stdin", "stdout", "stderr"}, s) {
return s, nil
}
return val, errors.Errorf("valid streams are STDIN, STDOUT and STDERR")
return val, errors.New("valid streams are STDIN, STDOUT and STDERR")
}
func toNetipAddrSlice(ips []string) []netip.Addr {
if len(ips) == 0 {
return nil
}
netIPs := make([]netip.Addr, 0, len(ips))
for _, ip := range ips {
addr, err := netip.ParseAddr(ip)
if err != nil {
continue
}
netIPs = append(netIPs, addr)
}
return netIPs
}

View File

@ -4,21 +4,31 @@ import (
"errors"
"fmt"
"io"
"net"
"net/netip"
"os"
"runtime"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types/container"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/go-connections/nat"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/moby/moby/api/types/container"
networktypes "github.com/moby/moby/api/types/network"
"github.com/spf13/pflag"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"
)
func mustParseMAC(s string) networktypes.HardwareAddr {
mac, err := net.ParseMAC(s)
if err != nil {
panic(err)
}
return networktypes.HardwareAddr(mac)
}
func TestValidateAttach(t *testing.T) {
valid := []string{
"stdin",
@ -48,7 +58,7 @@ func parseRun(args []string) (*container.Config, *container.HostConfig, *network
if err := flags.Parse(args); err != nil {
return nil, nil, nil, err
}
// TODO: fix tests to accept ContainerConfig
// TODO(dnephin): fix tests to accept ContainerConfig; see https://github.com/moby/moby/pull/31621
containerCfg, err := parse(flags, copts, runtime.GOOS)
if err != nil {
return nil, nil, nil, err
@ -350,13 +360,9 @@ func TestParseWithMacAddress(t *testing.T) {
if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
}
config, hostConfig, nwConfig := mustParse(t, validMacAddress)
if config.MacAddress != "92:d0:c6:0a:29:33" { //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as container-wide MacAddress, got '%v'",
config.MacAddress) //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
}
_, hostConfig, nwConfig := mustParse(t, validMacAddress)
defaultNw := hostConfig.NetworkMode.NetworkName()
if nwConfig.EndpointsConfig[defaultNw].MacAddress != "92:d0:c6:0a:29:33" {
if nwConfig.EndpointsConfig[defaultNw].MacAddress.String() != "92:d0:c6:0a:29:33" {
t.Fatalf("Expected the default endpoint to have the MacAddress '92:d0:c6:0a:29:33' set, got '%v'", nwConfig.EndpointsConfig[defaultNw].MacAddress)
}
}
@ -429,56 +435,55 @@ func TestParseHostnameDomainname(t *testing.T) {
}
func TestParseWithExpose(t *testing.T) {
invalids := map[string]string{
":": "invalid port format for --expose: :",
"8080:9090": "invalid port format for --expose: 8080:9090",
"/tcp": "invalid range format for --expose: /tcp, error: empty string specified for ports",
"/udp": "invalid range format for --expose: /udp, error: empty string specified for ports",
"NaN/tcp": `invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
"NaN-NaN/tcp": `invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
"8080-NaN/tcp": `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
"1234567890-8080/tcp": `invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
}
valids := map[string][]nat.Port{
"8080/tcp": {"8080/tcp"},
"8080/udp": {"8080/udp"},
"8080/ncp": {"8080/ncp"},
"8080-8080/udp": {"8080/udp"},
"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
}
for expose, expectedError := range invalids {
if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
t.Run("invalid", func(t *testing.T) {
tests := map[string]string{
":": `invalid range format for --expose: invalid start port ':': invalid syntax`,
"8080:9090": `invalid range format for --expose: invalid start port '8080:9090': invalid syntax`,
"/tcp": `invalid range format for --expose: invalid start port '': value is empty`,
"/udp": `invalid range format for --expose: invalid start port '': value is empty`,
"NaN/tcp": `invalid range format for --expose: invalid start port 'NaN': invalid syntax`,
"NaN-NaN/tcp": `invalid range format for --expose: invalid start port 'NaN': invalid syntax`,
"8080-NaN/tcp": `invalid range format for --expose: invalid end port 'NaN': invalid syntax`,
"1234567890-8080/tcp": `invalid range format for --expose: invalid start port '1234567890': value out of range`,
}
}
for expose, exposedPorts := range valids {
config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
if err != nil {
t.Fatal(err)
for expose, expectedError := range tests {
t.Run(expose, func(t *testing.T) {
_, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
assert.Error(t, err, expectedError)
})
}
if len(config.ExposedPorts) != len(exposedPorts) {
t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts))
})
t.Run("valid", func(t *testing.T) {
tests := map[string][]networktypes.Port{
"8080/tcp": {networktypes.MustParsePort("8080/tcp")},
"8080/udp": {networktypes.MustParsePort("8080/udp")},
"8080/ncp": {networktypes.MustParsePort("8080/ncp")},
"8080-8080/udp": {networktypes.MustParsePort("8080/udp")},
"8080-8082/tcp": {networktypes.MustParsePort("8080/tcp"), networktypes.MustParsePort("8081/tcp"), networktypes.MustParsePort("8082/tcp")},
}
for _, port := range exposedPorts {
if _, ok := config.ExposedPorts[port]; !ok {
t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts)
}
for expose, exposedPorts := range tests {
t.Run(expose, func(t *testing.T) {
config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
assert.NilError(t, err)
for _, port := range exposedPorts {
_, ok := config.ExposedPorts[port]
assert.Check(t, ok, "missing port %q in exposed ports: %#+v", port, config.ExposedPorts[port])
}
})
}
}
// Merge with actual published port
config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
if err != nil {
t.Fatal(err)
}
if len(config.ExposedPorts) != 2 {
t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
}
ports := []nat.Port{"80/tcp", "81/tcp"}
for _, port := range ports {
if _, ok := config.ExposedPorts[port]; !ok {
t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
})
t.Run("merge with published", func(t *testing.T) {
// Merge with actual published port
config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
assert.NilError(t, err)
assert.Check(t, is.Len(config.ExposedPorts, 2))
ports := []networktypes.Port{networktypes.MustParsePort("80/tcp"), networktypes.MustParsePort("81/tcp")}
for _, port := range ports {
_, ok := config.ExposedPorts[port]
assert.Check(t, ok, "missing port %q in exposed ports: %#+v", port, config.ExposedPorts[port])
}
}
})
}
func TestParseDevice(t *testing.T) {
@ -576,7 +581,6 @@ func TestParseNetworkConfig(t *testing.T) {
name string
flags []string
expected map[string]*networktypes.EndpointSettings
expectedCfg container.Config
expectedHostCfg container.HostConfig
expectedErr string
}{
@ -608,9 +612,9 @@ func TestParseNetworkConfig(t *testing.T) {
expected: map[string]*networktypes.EndpointSettings{
"net1": {
IPAMConfig: &networktypes.EndpointIPAMConfig{
IPv4Address: "172.20.88.22",
IPv6Address: "2001:db8::8822",
LinkLocalIPs: []string{"169.254.2.2", "fe80::169:254:2:2"},
IPv4Address: netip.MustParseAddr("172.20.88.22"),
IPv6Address: netip.MustParseAddr("2001:db8::8822"),
LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.2.2"), netip.MustParseAddr("fe80::169:254:2:2")},
},
Links: []string{"foo:bar", "bar:baz"},
Aliases: []string{"web1", "web2"},
@ -638,9 +642,9 @@ func TestParseNetworkConfig(t *testing.T) {
"net1": {
DriverOpts: map[string]string{"field1": "value1"},
IPAMConfig: &networktypes.EndpointIPAMConfig{
IPv4Address: "172.20.88.22",
IPv6Address: "2001:db8::8822",
LinkLocalIPs: []string{"169.254.2.2", "fe80::169:254:2:2"},
IPv4Address: netip.MustParseAddr("172.20.88.22"),
IPv6Address: netip.MustParseAddr("2001:db8::8822"),
LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.2.2"), netip.MustParseAddr("fe80::169:254:2:2")},
},
Links: []string{"foo:bar", "bar:baz"},
Aliases: []string{"web1", "web2"},
@ -649,15 +653,15 @@ func TestParseNetworkConfig(t *testing.T) {
"net3": {
DriverOpts: map[string]string{"field3": "value3"},
IPAMConfig: &networktypes.EndpointIPAMConfig{
IPv4Address: "172.20.88.22",
IPv6Address: "2001:db8::8822",
IPv4Address: netip.MustParseAddr("172.20.88.22"),
IPv6Address: netip.MustParseAddr("2001:db8::8822"),
},
Aliases: []string{"web3"},
},
"net4": {
MacAddress: "02:32:1c:23:00:04",
MacAddress: mustParseMAC("02:32:1c:23:00:04"),
IPAMConfig: &networktypes.EndpointIPAMConfig{
LinkLocalIPs: []string{"169.254.169.254"},
LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.169.254")},
},
},
},
@ -673,14 +677,13 @@ func TestParseNetworkConfig(t *testing.T) {
"field2": "value2",
},
IPAMConfig: &networktypes.EndpointIPAMConfig{
IPv4Address: "172.20.88.22",
IPv6Address: "2001:db8::8822",
IPv4Address: netip.MustParseAddr("172.20.88.22"),
IPv6Address: netip.MustParseAddr("2001:db8::8822"),
},
Aliases: []string{"web1", "web2"},
MacAddress: "02:32:1c:23:00:04",
MacAddress: mustParseMAC("02:32:1c:23:00:04"),
},
},
expectedCfg: container.Config{MacAddress: "02:32:1c:23:00:04"},
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
},
{
@ -695,10 +698,9 @@ func TestParseNetworkConfig(t *testing.T) {
expected: map[string]*networktypes.EndpointSettings{
"net1": {
Aliases: []string{"foobar"},
MacAddress: "52:0f:f3:dc:50:10",
MacAddress: mustParseMAC("52:0f:f3:dc:50:10"),
},
},
expectedCfg: container.Config{MacAddress: "52:0f:f3:dc:50:10"},
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
},
{
@ -745,7 +747,7 @@ func TestParseNetworkConfig(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
config, hConfig, nwConfig, err := parseRun(tc.flags)
_, hConfig, nwConfig, err := parseRun(tc.flags)
if tc.expectedErr != "" {
assert.Error(t, err, tc.expectedErr)
@ -753,9 +755,8 @@ func TestParseNetworkConfig(t *testing.T) {
}
assert.NilError(t, err)
assert.DeepEqual(t, config.MacAddress, tc.expectedCfg.MacAddress) //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
assert.DeepEqual(t, hConfig.NetworkMode, tc.expectedHostCfg.NetworkMode)
assert.DeepEqual(t, nwConfig.EndpointsConfig, tc.expected)
assert.DeepEqual(t, nwConfig.EndpointsConfig, tc.expected, cmpopts.EquateComparable(netip.Addr{}))
})
}
}
@ -1017,7 +1018,7 @@ func TestParseLabelfileVariables(t *testing.T) {
func TestParseEntryPoint(t *testing.T) {
config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
assert.NilError(t, err)
assert.Check(t, is.DeepEqual([]string(config.Entrypoint), []string{"anything"}))
assert.Check(t, is.DeepEqual(config.Entrypoint, []string{"anything"}))
}
func TestValidateDevice(t *testing.T) {

View File

@ -8,7 +8,8 @@ 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/container"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -16,8 +17,8 @@ type pauseOptions struct {
containers []string
}
// NewPauseCommand creates a new cobra.Command for `docker pause`
func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
// newPauseCommand creates a new cobra.Command for "docker container pause"
func newPauseCommand(dockerCLI command.Cli) *cobra.Command {
var opts pauseOptions
return &cobra.Command{
@ -26,20 +27,24 @@ func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args
return runPause(cmd.Context(), dockerCli, &opts)
return runPause(cmd.Context(), dockerCLI, &opts)
},
Annotations: map[string]string{
"aliases": "docker container pause, docker pause",
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(ctr container.Summary) bool {
ValidArgsFunction: completion.ContainerNames(dockerCLI, false, func(ctr container.Summary) bool {
return ctr.State != container.StatePaused
}),
DisableFlagsInUseLine: true,
}
}
func runPause(ctx context.Context, dockerCLI command.Cli, opts *pauseOptions) error {
apiClient := dockerCLI.Client()
errChan := parallelOperation(ctx, opts.containers, apiClient.ContainerPause)
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, container string) error {
_, err := apiClient.ContainerPause(ctx, container, client.ContainerPauseOptions{})
return err
})
var errs []error
for _, ctr := range opts.containers {

View File

@ -8,20 +8,15 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestRunPause(t *testing.T) {
cli := test.NewFakeCli(
&fakeClient{
containerPauseFunc: func(ctx context.Context, container string) error {
return nil
},
},
)
cli := test.NewFakeCli(&fakeClient{})
cmd := NewPauseCommand(cli)
cmd := newPauseCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs([]string{"container-id-1", "container-id-2"})
@ -41,13 +36,13 @@ func TestRunPause(t *testing.T) {
func TestRunPauseClientError(t *testing.T) {
cli := test.NewFakeCli(
&fakeClient{
containerPauseFunc: func(ctx context.Context, container string) error {
return fmt.Errorf("client error for container %s", container)
containerPauseFunc: func(ctx context.Context, container string, options client.ContainerPauseOptions) (client.ContainerPauseResult, error) {
return client.ContainerPauseResult{}, fmt.Errorf("client error for container %s", container)
},
},
)
cmd := NewPauseCommand(cli)
cmd := newPauseCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs([]string{"container-id-1", "container-id-2"})

View File

@ -5,15 +5,14 @@ import (
"fmt"
"net"
"sort"
"strconv"
"strings"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/go-connections/nat"
"github.com/fvbommel/sortorder"
"github.com/pkg/errors"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -23,8 +22,8 @@ type portOptions struct {
port string
}
// NewPortCommand creates a new cobra.Command for `docker port`
func NewPortCommand(dockerCli command.Cli) *cobra.Command {
// newPortCommand creates a new cobra.Command for "docker container port".
func newPortCommand(dockerCLI command.Cli) *cobra.Command {
var opts portOptions
cmd := &cobra.Command{
@ -36,12 +35,13 @@ func NewPortCommand(dockerCli command.Cli) *cobra.Command {
if len(args) > 1 {
opts.port = args[1]
}
return runPort(cmd.Context(), dockerCli, &opts)
return runPort(cmd.Context(), dockerCLI, &opts)
},
Annotations: map[string]string{
"aliases": "docker container port, docker port",
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
DisableFlagsInUseLine: true,
}
return cmd
}
@ -53,31 +53,28 @@ func NewPortCommand(dockerCli command.Cli) *cobra.Command {
// proto is specified. We should consider changing this to "any" protocol
// for the given private port.
func runPort(ctx context.Context, dockerCli command.Cli, opts *portOptions) error {
c, err := dockerCli.Client().ContainerInspect(ctx, opts.container)
c, err := dockerCli.Client().ContainerInspect(ctx, opts.container, client.ContainerInspectOptions{})
if err != nil {
return err
}
var out []string
if opts.port != "" {
port, proto, _ := strings.Cut(opts.port, "/")
if proto == "" {
proto = "tcp"
port, err := network.ParsePort(opts.port)
if err != nil {
return err
}
if _, err = strconv.ParseUint(port, 10, 16); err != nil {
return errors.Wrapf(err, "Error: invalid port (%s)", port)
}
frontends, exists := c.NetworkSettings.Ports[nat.Port(port+"/"+proto)]
frontends, exists := c.Container.NetworkSettings.Ports[port]
if !exists || len(frontends) == 0 {
return errors.Errorf("Error: No public port '%s' published for %s", opts.port, opts.container)
return fmt.Errorf("no public port '%s' published for %s", opts.port, opts.container)
}
for _, frontend := range frontends {
out = append(out, net.JoinHostPort(frontend.HostIP, frontend.HostPort))
out = append(out, net.JoinHostPort(frontend.HostIP.String(), frontend.HostPort))
}
} else {
for from, frontends := range c.NetworkSettings.Ports {
for from, frontends := range c.Container.NetworkSettings.Ports {
for _, frontend := range frontends {
out = append(out, fmt.Sprintf("%s -> %s", from, net.JoinHostPort(frontend.HostIP, frontend.HostPort)))
out = append(out, fmt.Sprintf("%s -> %s", from, net.JoinHostPort(frontend.HostIP.String(), frontend.HostPort)))
}
}
}

View File

@ -2,11 +2,13 @@ package container
import (
"io"
"net/netip"
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
)
@ -14,59 +16,59 @@ import (
func TestNewPortCommandOutput(t *testing.T) {
testCases := []struct {
name string
ips []string
ips []netip.Addr
port string
}{
{
name: "container-port-ipv4",
ips: []string{"0.0.0.0"},
ips: []netip.Addr{netip.MustParseAddr("0.0.0.0")},
port: "80",
},
{
name: "container-port-ipv6",
ips: []string{"::"},
ips: []netip.Addr{netip.MustParseAddr("::")},
port: "80",
},
{
name: "container-port-ipv6-and-ipv4",
ips: []string{"::", "0.0.0.0"},
ips: []netip.Addr{netip.MustParseAddr("::"), netip.MustParseAddr("0.0.0.0")},
port: "80",
},
{
name: "container-port-ipv6-and-ipv4-443-udp",
ips: []string{"::", "0.0.0.0"},
ips: []netip.Addr{netip.MustParseAddr("::"), netip.MustParseAddr("0.0.0.0")},
port: "443/udp",
},
{
name: "container-port-all-ports",
ips: []string{"::", "0.0.0.0"},
ips: []netip.Addr{netip.MustParseAddr("::"), netip.MustParseAddr("0.0.0.0")},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
inspectFunc: func(string) (container.InspectResponse, error) {
inspectFunc: func(string) (client.ContainerInspectResult, error) {
ci := container.InspectResponse{NetworkSettings: &container.NetworkSettings{}}
ci.NetworkSettings.Ports = nat.PortMap{
"80/tcp": make([]nat.PortBinding, len(tc.ips)),
"443/tcp": make([]nat.PortBinding, len(tc.ips)),
"443/udp": make([]nat.PortBinding, len(tc.ips)),
ci.NetworkSettings.Ports = network.PortMap{
network.MustParsePort("80/tcp"): make([]network.PortBinding, len(tc.ips)),
network.MustParsePort("443/tcp"): make([]network.PortBinding, len(tc.ips)),
network.MustParsePort("443/udp"): make([]network.PortBinding, len(tc.ips)),
}
for i, ip := range tc.ips {
ci.NetworkSettings.Ports["80/tcp"][i] = nat.PortBinding{
ci.NetworkSettings.Ports[network.MustParsePort("80/tcp")][i] = network.PortBinding{
HostIP: ip, HostPort: "3456",
}
ci.NetworkSettings.Ports["443/tcp"][i] = nat.PortBinding{
ci.NetworkSettings.Ports[network.MustParsePort("443/tcp")][i] = network.PortBinding{
HostIP: ip, HostPort: "4567",
}
ci.NetworkSettings.Ports["443/udp"][i] = nat.PortBinding{
ci.NetworkSettings.Ports[network.MustParsePort("443/udp")][i] = network.PortBinding{
HostIP: ip, HostPort: "5678",
}
}
return ci, nil
return client.ContainerInspectResult{Container: ci}, nil
},
})
cmd := NewPortCommand(cli)
cmd := newPortCommand(cli)
cmd.SetErr(io.Discard)
cmd.SetArgs([]string{"some_container", tc.port})
err := cmd.Execute()

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