Compare commits

..

439 Commits

Author SHA1 Message Date
fb31dfe7d5 fix(loader): Allows multiple protocols on one port
Some checks failed
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
build / build (push) Has been cancelled
build / plugins (push) Has been cancelled
2026-02-02 12:12:42 +01:00
915f5cf25d Merge pull request #5796 from vvoland/update-go-27.x
[27.x] update to go1.22.12
2025-02-05 19:24:30 +01:00
6ee2756538 update to go1.22.12
- https://github.com/golang/go/issues?q=milestone%3AGo1.22.12+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.22.11...go1.22.12

This minor release include 1 security fix following the security policy:

- crypto/elliptic: timing sidechannel for P-256 on ppc64le

  Due to the usage of a variable time instruction in the assembly implementation
  of an internal function, a small number of bits of secret scalars are leaked on
  the ppc64le architecture. Due to the way this function is used, we do not
  believe this leakage is enough to allow recovery of the private key when P-256
  is used in any well known protocols.

This is CVE-2025-22866 and Go issue https://go.dev/issue/71383.

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

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-02-04 20:36:34 +01:00
9f9e405801 Merge pull request #5759 from thaJeztah/27.x_vendor_docker_27.5.0
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x] vendor: github.com/docker/docker v27.5.0
2025-01-22 00:46:20 +01:00
d7cd22f0a2 Merge pull request #5762 from thaJeztah/27.x_bump_golang_1.22.11
[27.x] update to go1.22.11 (fix CVE-2024-45341, CVE-2024-45336)
2025-01-21 11:31:54 +01:00
0e2d4fe890 Merge pull request #5766 from thaJeztah/27.x_backport_bump_dev_tools
[27.x backport] Dockerfile: dev-container: update buildx v0.20.0, compose v2.32.4
2025-01-21 11:31:12 +01:00
fbc0a73b35 Dockerfile: update compose to v2.32.4
Update the compose cli plugin used in the dev-container

full diff: https://github.com/docker/compose/compare/v2.30.3...v2.32.4

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 1546f023fb)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-21 10:05:59 +01:00
777695f6d5 Dockerfile: update buildx to v0.20.0
Update the buildx cli plugin used in the dev-container

full diff: https://github.com/docker/buildx/compare/v0.18.0..v0.20.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 7b07242708)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-21 10:05:57 +01:00
092d23532c Merge pull request #5764 from vvoland/5763-27.x
[27.x backport] gha: Adjust release branches
2025-01-20 22:12:08 +01:00
fa1ba052d6 gha: Adjust release branches
Adjust all workflows to also run on branches like `27.x`

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 81b0bb58ba)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-01-20 18:24:03 +01:00
1ccfae7946 update to go1.22.11 (fix CVE-2024-45341, CVE-2024-45336)
go1.22.11 (released 2025-01-16) includes security fixes to the crypto/x509 and
net/http packages, as well as bug fixes to the runtime. See the Go 1.22.11
milestone on our issue tracker for details.

- https://github.com/golang/go/issues?q=milestone%3AGo1.22.11+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.22.10...go1.22.11

Hello gophers,

We have just released Go versions 1.23.5 and 1.22.11, minor point releases.

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

- crypto/x509: usage of IPv6 zone IDs can bypass URI name constraints

  A certificate with a URI which has a IPv6 address with a zone ID may
  incorrectly satisfy a URI name constraint that applies to the certificate
  chain.

  Certificates containing URIs are not permitted in the web PKI, so this
  only affects users of private PKIs which make use of URIs.

  Thanks to Juho Forsén of Mattermost for reporting this issue.

  This is CVE-2024-45341 and Go issue https://go.dev/issue/71156.

- net/http: sensitive headers incorrectly sent after cross-domain redirect

  The HTTP client drops sensitive headers after following a cross-domain redirect.
  For example, a request to a.com/ containing an Authorization header which is
  redirected to b.com/ will not send that header to b.com.

  In the event that the client received a subsequent same-domain redirect, however,
  the sensitive headers would be restored. For example, a chain of redirects from
  a.com/, to b.com/1, and finally to b.com/2 would incorrectly send the Authorization
  header to b.com/2.

  Thanks to Kyle Seely for reporting this issue.

  This is CVE-2024-45336 and Go issue https://go.dev/issue/70530.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-20 16:08:27 +01:00
397c2f87d5 vendor: github.com/docker/docker v27.5.0
no diff: same commit, but tagged; https://github.com/docker/docker/compare/38b84dce32c4...v27.5.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-20 11:01:41 +01:00
a187fa5d2d Merge pull request #5736 from thaJeztah/27.x_vendor_docker_27.5.0
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x] vendor: github.com/docker/docker 38b84dce32c4 (v27.5.0)
2025-01-10 17:13:57 +01:00
e32d69e31c vendor: github.com/docker/docker 38b84dce32c4 (v27.5.0)
full diff: https://github.com/docker/docker/compare/v27.5.0-rc.2...38b84dce32c45732606fe09ffebef8b29a783644

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-10 16:04:49 +01:00
2767e9eb46 Merge pull request #5732 from thaJeztah/27.x_vendor_docker_27.5.0-rc.2
[27.x] vendor: github.com/docker/docker v27.5.0-rc.2
2025-01-10 15:59:11 +01:00
a999a40714 Merge pull request #5735 from vvoland/5734-27.x
[27.x backport] gha/build: Publish bin image for release branches
2025-01-10 14:52:41 +01:00
b277537cbe gha/build: Publish bin image for release branches
We moved to the major release branches with a `.x` suffix and forgot to
adjust this workflow to run on branches like `27.x`.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 987befaeac)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-01-10 14:34:13 +01:00
919350df54 vendor: github.com/docker/docker v27.5.0-rc.2
no diff; same commit, but tagged;

https://github.com/docker/docker/compare/43fc912ef59a...v27.5.0-rc.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-10 10:12:58 +01:00
80f7848805 Merge pull request #5726 from thaJeztah/27.x_update_engine
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x] vendor: github.com/docker/docker 43fc912ef59a (v27.5.0-rc.2)
2025-01-07 16:33:16 +01:00
0d27375b07 Merge pull request #5723 from thaJeztah/27.x_backport_rm_oom_score_adj
[27.x backport] remove remnants of --oom-score-adj daemon config (docs, completion)
2025-01-03 22:42:19 +01:00
0ced103038 vendor: github.com/docker/docker 43fc912ef59a (v27.5.0-rc.2)
No changes in vendored code

full diff: https://github.com/docker/docker/compare/v27.5.0-rc.1...43fc912ef59a83054ea7f6706df4d53a7dea4d80

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-03 22:01:56 +01:00
f068c14063 contrib/completion: remove --oom-score-adj daemon flag
This flag was deprecated in docker v24.0, and no longer functional
since v25.0; fully removed in v26.0, so we can remove the docs
for this.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 2db88599fd)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-03 20:24:26 +01:00
5d973da95b docs: dockerd: --oom-score-adj flag
This flag was deprecated in docker v24.0, and no longer functional
since v25.0; fully removed in v26.0, so we can remove the docs
for this.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit eb5c507cd1)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-03 20:24:26 +01:00
498a416226 Merge pull request #5721 from thaJeztah/27.x_backport_docs
[27.x backport] docs, man: remove confusing example for "--isolation"
2025-01-03 19:31:21 +01:00
261d2241eb docs, man: remove confusing example for "--isolation"
This snippet was added in [docker@38ec5d8][1]. The intent was to indicate
that an empty value is equivalent to passing "default" as value. However,
passing the `--isolation` flag _without a value_ (i.e., no `=` specified)
will fail in many cases, as any string after it will be parsed as value
(e.g. `docker run --isolation busybox` would consider `busybox` as value).

This patch removes these lines as they add more confusion than addressing.

[1]: 38ec5d86a3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 7b7a4c020e)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-03 18:41:59 +01:00
e0e9feaa16 Merge pull request #5720 from thaJeztah/27.x_backport_fix-container-cp-test
[27.x backport] test-fixes
2025-01-03 14:48:34 +01:00
074820d7bb Fix cp test to separate source and destination
Currently the cp will tar from the same directory it will untar into
simultaneously. There is a race between reading the file and truncating
the file for write, however, the race will not show up with a large
enough buffer on the tar side if buffered before the copy begins.

Also removes the unnecessary deferred removal, the removal is handled by
cleanup and respects the no cleanup env.

Signed-off-by: Derek McGowan <derek@mcg.dev>
(cherry picked from commit 8c0cb30515)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-03 13:56:14 +01:00
8b340f4e5c TestRunCopyFromContainerToFilesystem: use Tar without options
Just a minor cleanup; use archive.Tar as we're not using other
options here.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit a8f83d5d99)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-03 13:56:09 +01:00
21cdefcec6 Merge pull request #5712 from thaJeztah/27.x_update_engine
[27.x] vendor: github.com/docker/docker v27.5.0-rc.1
2024-12-23 15:39:06 +01:00
0e053a5cee vendor: github.com/docker/docker v27.5.0-rc.1
no diff, only using tag instead of commit

diff: https://github.com/docker/docker/compare/c23af2910987...v27.5.0-rc.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-23 15:30:03 +01:00
7595cea3e5 Merge pull request #5706 from thaJeztah/27.x_bump_engine_27.5
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x] vendor: github.com/docker/docker c23af2910987 (27.x / v27.5.0-dev)
2024-12-20 19:54:59 +01:00
f009ed2b34 vendor: github.com/docker/docker c23af2910987 (27.x / v27.5.0-dev)
full diff: https://github.com/docker/docker/compare/v27.4.1...c23af2910987f9f3528f60140a5b3d7fedcabbe0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-20 19:35:56 +01:00
2dcdf75ad0 Merge pull request #5707 from thaJeztah/27.x_engine_deps
[27.x backport] update dependencies for docker 27.5.0
2024-12-20 19:35:22 +01:00
fd818019f5 vendor: go.opentelemetry.io/contrib/instrumentation/xxx v0.53.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 10c5a57927)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-20 18:46:42 +01:00
f264af6124 vendor: go.opentelemetry.io/otel v1.28.0
aligning all related packages to v1.28.0 as well

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 5e40d288c7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-20 18:43:46 +01:00
3e51dace7c [27.x] vendor: google.golang.org/genproto f6361c86f094
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-20 18:42:15 +01:00
2d3e97f32d vendor: github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0
full diff: https://github.com/grpc-ecosystem/grpc-gateway/compare/v2.16.0...v2.20.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9ba73a1a05)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-20 18:29:09 +01:00
d8f2d1b155 vendor: update prometheus dependencies
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 074d1028b5)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-20 18:25:39 +01:00
e3d2562114 vendor: github.com/go-logr/logr v1.4.2
full diff: https://github.com/go-logr/logr/compare/v1.4.1...v1.4.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 97ff1b7c0a)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-20 18:22:48 +01:00
bcec6cb71a vendor: github.com/cenkalti/backoff/v4 v4.3.0
full diff: https://github.com/cenkalti/backoff/compare/v4.2.1...v4.3.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 4c85feb4dd)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-20 18:22:26 +01:00
ef6bc69c04 vendor: github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161
documentation changes only, no changes in vendored code

full diff: d185dfc1b5...306776ec81

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 3b48a57b04)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-20 18:21:44 +01:00
68a89eaa17 Merge pull request #5701 from thaJeztah/27.x_update_docker
[27.x] vendor: github.com/docker/docker v27.4.1
2024-12-18 12:23:53 +00:00
d4dc4efef0 vendor: github.com/docker/docker v27.4.1
full diff: https://github.com/docker/docker/compare/v27.4.0...v27.4.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-18 13:14:35 +01:00
b9d17eaebb Merge pull request #5700 from thaJeztah/27.x_backport_remove_use_of_netfilter_fields
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x backport] cli/command/system: remove BridgeNfIptables, BridgeNfIp6tables in tests
2024-12-16 22:43:43 +01:00
a08a120bae cli/command/system: remove BridgeNfIptables, BridgeNfIp6tables in tests
This is a follow-up to 55e404e7a8, which
removed some warnings related to these fields.

These fields in the /info response were used to warn users if netfiltering
was not enabled on the host when the daemon started.  Starting with
[moby@db25b0d], detecting whether netfiltering  is enabled now
[happens when needed][1], making the state that's detected at startup
irrelevant.

These fields will therefore be deprecated in future, but we can start
removing their use in tests.

[moby@db25b0d]: db25b0dcd0
[1]: 944e403502/libnetwork/drivers/bridge/setup_bridgenetfiltering.go (L16-L77)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ffe0354c2c)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-16 22:19:20 +01:00
4870b3d067 Merge pull request #5699 from thaJeztah/27.x_backport_remove_system_isabs
[27.x backport] cli/command/container: use local copy of pkg/system.IsAbs
2024-12-16 18:37:44 +01:00
d3b59fbd93 cli/command/container: use local copy of pkg/system.IsAbs
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 1eda498786)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-16 17:54:05 +01:00
ac40240c02 Merge pull request #5685 from thaJeztah/27.x_backport_bump_xx
[27.x backport] update xx to v1.6.1 for compatibility with alpine 3.21
2024-12-13 14:38:52 +01:00
3fa9480d26 Merge pull request #5690 from thaJeztah/27.x_backport_bump_gomd2man
[27.x backport] update go-md2man to v2.0.5
2024-12-13 13:13:22 +01:00
fce7c04971 Merge pull request #5692 from thaJeztah/27.x_backport_remove_netfilter_warnings
[27.x backport] cli/command/system: remove netfilter warnings from tests
2024-12-13 13:12:33 +01:00
70815c1aa9 cli/command/system: remove netfilter warnings from tests
These warnings will no longer be returned by the daemon, so remove
them from the tests as well to make them more representative of
reality.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 55e404e7a8)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-13 10:32:25 +01:00
12d98b057f update go-md2man to v2.0.5
full diff: https://github.com/cpuguy83/go-md2man/compare/v2.0.4...v2.0.5

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit eaa8b5716d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-13 01:24:27 +01:00
f9783ec5c6 update xx to v1.6.1 for compatibility with alpine 3.21
This fixes compatibility with alpine 3.21

- Fix additional possible `xx-cc`/`xx-cargo` compatibility issue with Alpine 3.21
- Support for Alpine 3.21
- Fix `xx-verify` with `file` 5.46+
- Fix possible error taking lock in `xx-apk` in latest Alpine without `coreutils`

full diff: https://github.com/tonistiigi/xx/compare/v1.5.0...v1.6.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 1e51ae7af2)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-13 00:58:52 +01:00
27cf4d7d85 Merge pull request #5681 from thaJeztah/27.x_update_docker
[27.x] vendor: github.com/docker/docker v27.4.0
2024-12-09 16:20:00 +00:00
6377f54f27 vendor: github.com/docker/docker v27.4.0
no diff, as it's the same commit

full diff: https://github.com/docker/docker/compare/v27.4.0-rc.4...v27.4.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-09 17:02:04 +01:00
0a1f8e414b Merge pull request #5670 from thaJeztah/27.x_vendor_docker_27.4.0-rc.4
[27.x] vendor: github.com/docker/docker v27.4.0-rc.4
2024-12-05 10:37:00 +01:00
dc8511cd60 vendor: github.com/docker/docker v27.4.0-rc.4
https://github.com/docker/docker/compare/v27.4.0-rc.3...v27.4.0-rc.4

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-05 09:35:02 +01:00
bde2b89313 Merge pull request #5669 from thaJeztah/27.x_update_go_1.22.10
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x] update to go1.22.10
2024-12-04 13:15:36 +01:00
3284a80b05 update to go1.22.10
go1.22.10 (released 2024-12-03) includes fixes to the runtime and the syscall
package. See the Go 1.22.10 milestone on our issue tracker for details.

- https://github.com/golang/go/issues?q=milestone%3AGo1.22.10+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.22.9...go1.22.10

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-12-04 10:36:25 +01:00
b7064a2758 Merge pull request #5661 from thaJeztah/27.x_backport_remove_repoinfo_class
[27.x backport] cli/trust: GetNotaryRepository: remove uses of RepositoryInfo.Class
2024-12-02 16:15:46 +01:00
67b6fe0b55 Merge pull request #5649 from thaJeztah/27.x_update_engine
[27.x] vendor: github.com/docker/docker v27.4.0-rc.3
2024-12-02 09:22:21 +01:00
5a0508ccc8 cli/trust: GetNotaryRepository: remove uses of RepositoryInfo.Class
The Class field was added because  Docker Hub registry required a special
scope to be set for pulling plugins;

    HTTP/1.1 401 Unauthorized
    ...
    Www-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository(plugin):vieux/sshfs:pull",error="insufficient_scope"

This is no longer a requirement, and the field is no longer set.

updates 0ba820ed0b

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ed9fcf31e6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-30 14:47:06 +01:00
9348385006 vendor: github.com/docker/docker v27.4.0-rc.3
no changes in vendored code

full diff: https://github.com/docker/docker/compare/v27.4.0-rc.2...v27.4.0-rc.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-29 23:23:05 +01:00
9ea09fd364 Merge pull request #5652 from vvoland/5651-27.x
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x backport] cli-plugins: Fix searching inaccessible directories
2024-11-29 15:54:54 +01:00
0c9e0b43ce Merge pull request #5658 from thaJeztah/27.x_backport_docs-fix-screwy-dockerd-rendering
[27.x backport] docs: fix janky rendering of toc on docs.docker.com
2024-11-29 15:54:17 +01:00
f4fec76472 docs: fix janky rendering of toc on docs.docker.com
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 0f058041c4)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-29 14:50:48 +01:00
6fd48251ce Merge pull request #5654 from Benehiko/fix-run-ctx-27.x
[27.x backport] fix: ctx on run image pull
2024-11-29 14:47:53 +01:00
530cf098de test: run does not have error status on 27.x branch
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2024-11-29 14:27:35 +01:00
26da5bf025 fix: ctx should cancel image pull on run
This patch fixes the context cancellation
behaviour for the `runContainer` function,
specifically the `createContainer` function
introduced in this commit 991b1303da.

It delays stripping the `cancel` from the context
passed into the `runContainer` function so that
the `createContainer` function can be cancelled
gracefully by a SIGTERM/SIGINT.

This is especially true when the requested image
does not exist and `docker run` needs to `pull`
the image before creating the container.

Although this patch does gracefully cancel
the `runContainer` function it does not address
the root cause. Some functions in the call path
are not context aware, such as `pullImage`.

Future work would still be necessary to ensure
a consistent behaviour in the CLI.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
(cherry picked from commit 30a73ff19c)
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2024-11-29 14:27:24 +01:00
790d09e8dc cli-plugins: Simplify addPluginCandidatesFromDir
The returned error is always nil now, so just remove it.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit fcd94feefb)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-11-28 14:34:24 +01:00
5476e9e1dc cli-plugins: Fix searching inaccessible directories
Fix a case where one inaccessible plugin search path stops the whole
search and prevents latter paths from being scanned.

Remove a preliminary `Stat` call that verifies whether path is an actual
directory and is accessible.
It's unneeded and doesn't actually check whether the directory can be
listed or not.
`os.ReadDir` will fail in such case anyway, so just attempt to do that
and ignore any encountered error, instead of erroring out the whole
plugin candidate listing.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 6de3d71ab6)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-11-28 14:34:22 +01:00
ce03e25b14 Merge pull request #5640 from austinvazquez/cherry-pick-07e5ddd05441340dd044c14019dca22fe024a111-to-27.x
[27.x backport] update golangci-lint to v1.62.0
2024-11-22 18:25:07 +01:00
47a4f70d8a update golangci-lint to v1.62.0
full diff: https://github.com/golangci/golangci-lint/compare/v1.61.0...v1.62.0
Changelog: https://golangci-lint.run/product/changelog/#v1620

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 07e5ddd054)
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-11-22 01:11:55 +00:00
fa845f4a3c Merge pull request #5623 from thaJeztah/27.x_bump_moby_27.4.0-rc.2-dev
[27.x] vendor: github.com/docker/docker v27.4.0-rc.2
2024-11-20 11:57:02 +01:00
6b9fb59d31 [27.x] vendor: github.com/docker/docker v27.4.0-rc.2
no changes in vendored code

full diff: https://github.com/docker/docker/compare/v27.4.0-rc.1...v27.4.0-rc.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-19 20:38:39 +01:00
af56ef5b07 Merge pull request #5635 from thaJeztah/27.x_backport_improve_noflickr
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
2024-11-19 13:58:27 +01:00
39d73afbdd Optimise docker stats to not require clearing the whole screen
Instead of clearing the whole screen and then writing the new stats,
we now write the new stats on top of the old text, and then clear
the remaining text.

This is a more efficient way to update the stats, as it avoids the
flickering that happens when the screen is cleared and rewritten.

Signed-off-by: Giedrius Jonikas <giedriusj1@gmail.com>
(cherry picked from commit cb2f95ceee)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-19 12:49:28 +01:00
c6537362af Merge pull request #5631 from thaJeztah/27.x_bump_grpc
[27.x] vendor: google.golang.org/grpc v1.66.3
2024-11-19 09:55:33 +01:00
877668c92a [27.x] vendor: google.golang.org/grpc v1.66.3
- transport: Fix reporting of bytes read while reading headers
- xds/server: Fix xDS Server leak

full diff: https://github.com/grpc/grpc-go/compare/v1.66.2...v1.66.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-19 00:42:16 +01:00
8d1bacae3e Merge pull request #5622 from thaJeztah/27.x_backport_update_buildtags
[27.x backport] update go:build tags to use go1.22, fix missing go:build tag
2024-11-15 11:25:12 +01:00
cfe6090d5d cli/command/container: fix missing go:build tag
make shell
    make -C ./internal/gocompat/

    GO111MODULE=on go test -v
    # github.com/docker/cli/cli/command/container
    ../../cli/command/container/completion.go:37:28: implicit function instantiation requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/command/container/completion.go:82:25: implicit function instantiation requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/command/container/completion.go:92:27: implicit function instantiation requires go1.18 or later (-lang was set to go1.16; check go.mod)
    FAIL	gocompat [build failed]

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit d1d5353269)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-15 11:10:46 +01:00
3df40364f6 update go:build tags to use go1.22
commit 4a7b04d412 configured golangci-lint
to use go1.23 semantics, which enabled the copyloopvar linter.

go1.22 now creates a copy of variables when assigned in a loop; make sure we
don't have files that may downgrade semantics to go1.21 in case that also means
disabling that feature; https://go.dev/ref/spec#Go_1.22

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 7c80e4f938)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-15 11:10:35 +01:00
c5d93f53a7 Merge pull request #5619 from thaJeztah/27.x_bump_moby_27.4.0-rc.1
[27.x] vendor: github.com/docker/docker v27.4.0-rc.1
2024-11-15 09:25:00 +00:00
a4dc3d78e5 vendor: github.com/docker/docker v27.4.0-rc.1
no diff, as it's the same commit, but tagged:

https://github.com/docker/docker/compare/5765e9f35b00...v27.4.0-rc.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 23:14:39 +01:00
75876b4e20 Merge pull request #5618 from thaJeztah/27.x_backport_container_completions
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x backport] Improve Cobra completions for run and create
2024-11-14 18:25:35 +01:00
69bc2b1f0c Merge pull request #5545 from thaJeztah/27.x_vendor_moby
[27.x] vendor: github.com/docker/docker 5765e9f35b00 (v27.4-dev)
2024-11-14 18:25:03 +01:00
955e003345 Handle null completions with a default callback
Credits to thaJeztah

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 06260e68f3)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:04:55 +01:00
ce77f4a545 Add completion for --volume-driver
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 4525fe37b4)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:04:54 +01:00
e20bcf3565 Add completion for --cgroupns
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit db0ed1e216)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:04:54 +01:00
78544cd587 Add completion for --uts
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 2915749279)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:04:54 +01:00
d11291966d Add completion for --log-driver and --log-opt
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 3a2503fa43)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:04:54 +01:00
25ff89519f Add completion for --security-opt
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 9a9ae231a9)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:04:54 +01:00
83303411ee Add completion for --detach-keys
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 5f7c43e5e6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:04:53 +01:00
e25548d1c6 Add completion for --userns
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 3292afe6e6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:04:53 +01:00
99e60c4e39 Add completion for --ulimit
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 5d709a8d9f)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:04:53 +01:00
6d087eb91b Add completion for --storage-opt
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 2d89339b34)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:04:53 +01:00
83f79c3e86 Add completion for --pid
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit ac7bde6f64)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:04:48 +01:00
6f439ea4a4 Add completion for --link
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit e513454244)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:00:47 +01:00
7e35be13ea Add completion for --ipc
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit c555327f0b)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:00:47 +01:00
f9ccc50304 Add completion for --attach
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit b598ec8cdb)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:00:47 +01:00
38e37778b0 Share the container completions
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 761d76750c)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 17:00:47 +01:00
49b6551e97 [27.x] vendor: github.com/docker/docker 5765e9f35b00 (v27.4-dev)
full diff: https://github.com/docker/docker/compare/v27.3.1...5765e9f35b0016dbd931efe9f23cb6bfc7b00bc3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 16:53:27 +01:00
fd53fe7f47 Merge pull request #5613 from thaJeztah/27.x_backport_bump_golangci_lint
[27.x backport] bump golangci-lint to v1.61.0 and cleanup config
2024-11-14 16:48:41 +01:00
929e861812 Merge pull request #5614 from thaJeztah/27.x_backport_completion-events--filter 2024-11-14 15:42:28 +00:00
8caf347188 Add tests for completions that call the API
Some small adjustments for this branch due to some times being renamed
in master;

    63.76 cli/command/system/cmd.go:1: : # github.com/docker/cli/cli/command/system [github.com/docker/cli/cli/command/system.test]
    63.76 cli/command/system/client_test.go:22:79: undefined: container.Summary
    63.76 cli/command/system/client_test.go:38:103: undefined: container.Summary
    63.76 cli/command/system/client_test.go:42:21: undefined: container.Summary
    63.76 cli/command/system/completion_test.go:30:86: undefined: container.Summary
    63.76 cli/command/system/completion_test.go:31:25: undefined: container.Summary
    63.76 cli/command/system/completion_test.go:42:86: undefined: container.Summary (typecheck)

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit e1c5180dba)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 14:55:02 +01:00
9e7d01bfb3 Add completion for events --filter
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit d4f4cf1418)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 14:53:54 +01:00
8be6bec27d Merge pull request #5611 from thaJeztah/27.x_backport_login_minor_refactor
[27.x backport] cli/command: PromptUserForCredentials: assorted minor improvements and (linting) fixes
2024-11-14 14:51:17 +01:00
d515955b62 Merge pull request #5616 from thaJeztah/27.x_backport_bump_compose_buildx
[27.x backport] Dockerfile: update buildx to v0.18.0, compose to v2.30.3
2024-11-14 14:42:31 +01:00
d5b327258f Merge pull request #5612 from thaJeztah/27.x_backport_docs
[27.x backport] assorted docs updates
2024-11-14 10:42:14 +01:00
9128f7b2d5 Dockerfile: update compose to v2.30.3
Update the compose cli plugin used in the dev-container

full diff: https://github.com/docker/compose/compare/v2.29.7...v2.30.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 3dd7621240)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 10:37:44 +01:00
0529d64f7f Dockerfile: update buildx to v0.18.0
Update the buildx cli plugin used in the dev-container

full diff: https://github.com/docker/buildx/compare/0.17.1..0.18.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 4242cda826)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 10:37:44 +01:00
2958a27e0f Merge pull request #5610 from thaJeztah/27.x_backport_moby_deps
[27.x backport] dependency updates for moby/moby
2024-11-14 10:27:53 +01:00
8fe93724a3 bump golangci-lint to v1.61.0
Also updating a linter that was deprecated;

    The linter 'exportloopref' is deprecated (since v1.60.2) due to: Since Go1.22 (loopvar) this linter is no longer relevant. Replaced by copyloopvar.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9af049c618)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 09:44:37 +01:00
df7113d7eb [27.x] cli: fix non-constant format string in call to errors.Errorf (govet)
cli/required.go:17:24: printf: non-constant format string in call to github.com/docker/cli/vendor/github.com/pkg/errors.Errorf (govet)
     		return errors.Errorf("\n" + strings.TrimRight(cmd.UsageString(), "\n"))
     		                     ^

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-14 09:42:49 +01:00
35122a0692 golangci-lint: update comment, and disable "exclude-dirs-use-default"
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 745629bd55)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:40:37 +01:00
1890ea0762 golangci-lint: move gosec excludes to linters-settings
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 7451339ab0)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:40:37 +01:00
7a906196ed golangci-lint: enable G204, add #nosec comments instead
There's only 3 locations where it's hit, so putting #gosec ignore comments
in those locations.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 020f3a7ad9)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:40:37 +01:00
2734299993 docs: update example redis tags from 3.0.x to 7.4.x
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 172f340112)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:37:40 +01:00
9d5a2a6b66 docs: Correct run exit code 126 description
The command to run inside the container is `/etc`. The semicolon is a
statement terminator, which ends the command `docker run busybox /etc`,
while `echo $?` prints the exit code of that full docker command.

Having this mistake could confuse someone who thinks that `/etc; echo
$?` is all run inside the container, which wouldn't help the reader
understand the exit code of the `docker run` command itself.

Signed-off-by: Noah Silas <noah@hustle.com>
(cherry picked from commit 0c999fe95b)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:37:10 +01:00
0dd07f0bfd docs: Link supported Go duration strings
Signed-off-by: Paul Rogalski <mail@paul-rogalski.de>
(cherry picked from commit c70b2165a9)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:36:58 +01:00
168a4bdc05 cli/command: PromptUserForCredentials: suppress unhandled errors
Keep the linters (and my IDE) happy; these errors should be safe to ignore.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 4b7a1e4613)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:32:41 +01:00
ddeb7eb4ed cli/command: PromptUserForCredentials: use consts for all hints
This message resulted in code-lines that were too long; move it to a
const together with the other hint. While at it, also suppress unhandled
error, and touch-up the code-comment.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 378a3d7d36)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:32:41 +01:00
da3a1c3027 cli/command: PromptUserForCredentials: print error on terminal restore fail
If restoring the terminal state fails, "echo" no longer works, which means
that anything the user types is no longer shown. The login itself may already
have succeeded, so we should not fail the command, but it's good to inform
the user that this happened, which may give them a clue why things no longer
work as they expect them to work.

With this patch:

    docker login -u yourname
    Password:
    Error: failed to restore terminal state to echo input: something bad happened

    Login Succeeded

We should consider printing instructions how  to restore this manually (other
than restarting the shell). e.g., 'run stty echo' when in a Linux or macOS shell,
but PowerShell and CMD.exe may need different instructions.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 3d8b49523d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:32:41 +01:00
1377310110 cli/command: PromptUserForCredentials: always trim password
we don't support empty passwords; when prompting the user for a password,
we already trim the result, but we didn't do the same for a password that's
passed through stdin or through the `-p` / `--password` flag.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit a21a5f4243)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:32:41 +01:00
78dbcca264 cli/command: PromptUserForCredentials: move trimming where it's used
- move trimming defaultUsername inside the if-branch, as it's the only
  location where the result of the trimmed username is use.
- do the reverse for trimming argUser, because the result of trimming
  argUser is used outside of the if-branch (not just for the condition).
  putting it inside the condition makes it easy to assume the result is
  only used locally.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit eda78e9cdc)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:32:41 +01:00
97c25b7574 cli/command: PromptUserForCredentials: move "post" check for empty name
move the "post" check for username being empty inside the branch
that's handling the username, as it's the only branch where username
is mutated after checking if it's empty.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 581cf36bd4)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:32:41 +01:00
05455f8505 cli/command: PromptUserForCredentials: inline isDefaultRegistry
remove isDefaultRegistry and inline it where it's used; the code-comment
already outlines what we're looking for, so the intermediate var didn't
add much currently.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit a55cfe5f82)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:32:40 +01:00
d23ab524c3 cli/command: PromptUserForCredentials: remove named output variables
This function has multiple conditional branches, which makes it harder
to see at a glance whether authConfig may be partially populated. This
patch instead returns a fresh instance for error returns to prevent any
confusion.

It also removes the named output variables, as they're now no longer used,
and the returned types should already be descriptive enough to understand
what's returned.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 3a8485085d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:32:40 +01:00
46844b4f1d vendor: github.com/klauspost/compress v1.17.11
full diff: https://github.com/klauspost/compress/compare/v1.17.9...v1.17.11

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit e3942d46a0)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:01:16 +01:00
3f28d05292 vendor: google.golang.org/protobuf v1.35.1
full diff: https://github.com/protocolbuffers/protobuf-go/compare/v1.34.1...v1.35.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 1bba009944)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 23:00:49 +01:00
2521abdb1b vendor: golang.org/x/sys v0.26.0
full diff: https://github.com/golang/sys/compare/v0.25.0...v0.26.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 02b92c699d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-13 22:59:20 +01:00
eb986ae71b Merge pull request #5603 from dvdksn/bp_5600
[27.x backport] docs: change link to desktop docs
2024-11-07 13:57:54 +01:00
f39012209b Merge pull request #5602 from vvoland/update-go-27.x
[27.x] update to go1.22.9
2024-11-07 13:54:26 +01:00
e15a979e3d docs: change link to desktop docs
Signed-off-by: aevesdocker <allie.sadler@docker.com>
(cherry picked from commit 1440f9f8cf)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-11-07 13:29:04 +01:00
35c87e326c update to go1.22.9
- https://github.com/golang/go/issues?q=milestone%3AGo1.22.9+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.22.8...go1.22.9

go1.22.9 (released 2024-11-06) includes fixes to the linker. See the
[Go 1.22.9 milestone](https://github.com/golang/go/issues?q=milestone%3AGo1.22.9+label%3ACherryPickApproved)
for details.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-11-07 13:23:41 +01:00
f4f328b84b Merge pull request #5588 from vvoland/5586-27.x
[27.x backport] Buffer 'docker stats' text to avoid terminal flickering
2024-10-31 22:07:21 +01:00
470ab05503 Buffer 'docker stats' text to avoid terminal flickering
This change reduces the flickering of the terminal when
running `docker stats` by buffering the formatted stats
text and printing it in one write.

Should also consume less CPU as we now only have to issue
a single syscall to write the stats text to the terminal.

Signed-off-by: Giedrius Jonikas <giedriusj1@gmail.com>
(cherry picked from commit 0b16070ae6)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-10-31 14:29:38 +01:00
968506fd0f Merge pull request #5569 from thaJeztah/27.x_backport_login_idempotent 2024-10-22 16:18:18 +01:00
6736be779a cli/config/credentials: add test for save being idempotent
Test case for d3f6867e4d

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 3c78069240)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-22 12:23:27 +02:00
720da3c65a cil/config/credentials: remove newStore() test-utility
This function was names slightly confusing, as it returns a fakeStore,
and it didn't do any constructing, so didn't provide value above just
constructing the type.

I'm planning to add more functionality to the fakeStore, but don't want
to maintain a full-fledged constructor for all of that, so let's remove
this utility.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 0dd6f7f1b3)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-22 12:15:34 +02:00
05c860e866 Merge pull request #5548 from thaJeztah/27.x_backport_plugin_better_error
[27.x backport] cli/command/plugins: use errors.Join instead of custom cli.Errors, and deprecate cli.Errors
2024-10-22 10:42:38 +02:00
365e7a5a4e cli/config/credentials: skip saving config-file if credentials didn't change
Before this change, the config-file was always updated, even if there
were no changes to save. This could cause issues when the config-file
already had credentials set and was read-only for the current user.

For example, on NixOS, this poses a problem because `config.json` is a
symlink to a write-protected file;

    $ readlink ~/.docker/config.json
    /home/username/.config/sops-nix/secrets/ghcr_auth

    $ readlink -f ~/.docker/config.json
    /run/user/1000/secrets.d/28/ghcr_auth

Which causes `docker login` to fail, even if no changes were to be made;

    Error saving credentials: rename /home/derek/.docker/config.json2180380217 /home/username/.config/sops-nix/secrets/ghcr_auth: invalid cross-device link

This patch updates the code to only update the config file if changes
were detected. It there's nothing to save, it skips updating the file,
as well as skips printing the warning about credentials being stored
insecurely.

With this patch applied:

    $ docker login -u yourname
    Password:

    WARNING! Your credentials are stored unencrypted in '/root/.docker/config.json'.
    Configure a credential helper to remove this warning. See
    https://docs.docker.com/go/credential-store/

    Login Succeeded

    $ docker login -u yourname
    Password:
    Login Succeeded

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit d3f6867e4d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-22 00:21:18 +02:00
79113a3db5 Merge pull request #5564 from austinvazquez/cherry-pick-35d7b1a7a6426080f44e044b6564cee990b35eab-to-27.x
[27.x backport] cli/command/container: set empty args in tests and discard output
2024-10-21 23:21:51 +02:00
df7e6e5c5f cli/command/container: TestWaitExitOrRemoved use subtests
=== RUN   TestWaitExitOrRemoved
    === RUN   TestWaitExitOrRemoved/normal-container
    === RUN   TestWaitExitOrRemoved/give-me-exit-code-42
    === RUN   TestWaitExitOrRemoved/i-want-a-wait-error
    time="2024-10-13T18:48:14+02:00" level=error msg="Error waiting for container: removal failed"
    === RUN   TestWaitExitOrRemoved/non-existent-container-id
    time="2024-10-13T18:48:14+02:00" level=error msg="error waiting for container: no such container: non-existent-container-id"
    --- PASS: TestWaitExitOrRemoved (0.00s)
        --- PASS: TestWaitExitOrRemoved/normal-container (0.00s)
        --- PASS: TestWaitExitOrRemoved/give-me-exit-code-42 (0.00s)
        --- PASS: TestWaitExitOrRemoved/i-want-a-wait-error (0.00s)
        --- PASS: TestWaitExitOrRemoved/non-existent-container-id (0.00s)
    PASS

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 35d7b1a7a6)
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-10-21 15:53:20 +00:00
254b966b0d cli/command/container: set empty args in tests and discard output
Prevent some tests from failing when running from a pre-compiled
testbinary, and discard output to make the output less noisy.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 3b38dc67be)
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-10-21 15:53:09 +00:00
220033dd80 Merge pull request #5552 from thaJeztah/27.x_backport_fix_ConfigureAuth_deprecation 2024-10-21 16:42:49 +01:00
dd013610c6 Merge pull request #5563 from vvoland/5554-27.x
[27.x backport] Fix bash completion for `events --filter daemon=`
2024-10-21 11:37:57 +02:00
dc36908ec5 Fix bash completion for events --filter daemon=
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 3f7b156c85)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-10-21 11:12:35 +02:00
a8a8f68268 cli/command: ConfigureAuth: fix deprecation comment
Deprecation comments must have an empty line before them, otherwise tools
and linters may not recognise them. While fixing this, also updated the
reference to PromptUserForCredentials to be a docs-link to make it clickable.

Updates 6e4818e7d6.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 54e3685bcd)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-19 13:10:31 +02:00
b3a55ecbba cli: deprecate Errors type
The Errors type is no longer used by the CLI itself, and this custom
"multi-error" implementation had both limitations (empty list not being
`nil`), as well as formatting not being great. All of this making it not
something to recommend, and better handled with Go's stdlib.

As far as I could find, there's no external consumers of this, but let's
deprecate first, and remove in the next release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit d3bafa5f3e)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-19 10:36:33 +02:00
d3bf82db86 cli/command/plugins: use errors.Join instead of custom cli.Errors
This command was using a custom "multi-error" implementation, but it
had some limitations, and the formatting wasn't great.

This patch replaces it with Go's errors.Join.

Before:

    docker plugin remove one two three
    Error response from daemon: plugin "one" not found, Error response from daemon: plugin "two" not found, Error response from daemon: plugin "three" not found

After:

    docker plugin remove one two three
    Error response from daemon: plugin "one" not found
    Error response from daemon: plugin "two" not found
    Error response from daemon: plugin "three" not found

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 71ebbb81ae)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-19 10:36:33 +02:00
0008d79159 Merge pull request #5543 from thaJeztah/27.x_backport_bump_swarmkit
[27.x backport] vendor: github.com/moby/swarmkit/v2 v2.0.0-20241017191044-e8ecf83ee08e
2024-10-18 11:50:01 +02:00
16201b3eb2 vendor: github.com/moby/swarmkit/v2 v2.0.0-20241017191044-e8ecf83ee08e
- add Unwrap error to custom error types
- removes dependency on github.com/rexray/gocsi
- fix CSI plugin load issue

full diff: ea1a7cec35...e8ecf83ee0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit cbbb917323)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-18 11:38:19 +02:00
4241e00d1d Merge pull request #5540 from thaJeztah/27.x_backport_completion_improvements
[27.x backport] assorted improvements for shell completion
2024-10-18 10:48:48 +02:00
b018e55ca4 Only complete removable containers if --force is not given
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 147630a309)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:43:03 +02:00
e2831282ee completion: add test for VolumeNames
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit e1c472a436)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:43:03 +02:00
b68bf3afe4 completion: add test for NetworkNames
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 302d73f990)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:43:03 +02:00
77d002ae25 completion: add test for ImageNames
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ab418a38d8)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:43:01 +02:00
95e329d3e3 completion: add test for ContainerNames
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit f3b4094eb0)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:42:10 +02:00
02da13fb18 completion: add test for NoComplete
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit be197da6b8)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:11:05 +02:00
62230c7ec2 completion: add test for FromList
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 51713196c9)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:11:05 +02:00
954dba4482 completion: add test for FileNames
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit a5ca5b33f1)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:11:05 +02:00
7d1fa132fb completion: add test for EnvVarNames
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 8f2e5662e7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:11:05 +02:00
b6e7eba447 completion: ContainerNames: don't panic on nil filter
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b8cddc63ad)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:11:04 +02:00
099d4baeed cli/command/image: add shell completion for --platform flags
With this patch, completion is provided for `--platform` flags:

    docker pull --platform<TAB>
    linux           linux/amd64     linux/arm/v5    linux/arm/v7    linux/arm64/v8  linux/riscv64   wasip1          windows
    linux/386       linux/arm       linux/arm/v6    linux/arm64     linux/ppc64le   linux/s390x     wasip1/wasm     windows/amd64

Note that `docker buildx build` (with BuildKit) does not yet provide completion;
it's provided through buildx, and uses a different format (accepting multiple
comma-separated platforms). Interestingly, tab-completion for `docker build`
currently uses completion for non-buildkit, and has some other issues that may
have to be looked into.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit d42cf96e15)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:10:59 +02:00
dd4b370d3c cli/command/container: add shell completion for --platform flags
With this patch, completion is provided for `--platform` flags:

    docker run --platform<TAB>
    linux           linux/amd64     linux/arm/v5    linux/arm/v7    linux/arm64/v8  linux/riscv64   wasip1          windows
    linux/386       linux/arm       linux/arm/v6    linux/arm64     linux/ppc64le   linux/s390x     wasip1/wasm     windows/amd64

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 8c7f713db6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:10:59 +02:00
64e98fa375 cli/command/completion: add Platforms
Add a utility for completing platform strings.

Platforms offers completion for platform-strings. It provides a non-exhaustive
list of platforms to be used for completion. Platform-strings are based on
[runtime.GOOS] and [runtime.GOARCH], but with (optional) variants added. A
list of recognised os/arch combinations from the Go runtime can be obtained
through "go tool dist list".

Some noteworthy exclusions from this list:

  - arm64 images ("windows/arm64", "windows/arm64/v8") do not yet exist for windows.
  - we don't (yet) include `os-variant` for completion (as can be used for Windows images)
  - we don't (yet) include platforms for which we don't build binaries, such as
    BSD platforms (freebsd, netbsd, openbsd), android, macOS (darwin).
  - 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.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ce1aebcc30)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:10:58 +02:00
e6c6658563 cli/command/container: add unit tests for completion helpers
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit d49e72c0ac)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:08:29 +02:00
52ac568385 cli/container: use github.com/moby/sys/capability for completions
We used a hard-coded list of capabilities that we copied from containerd,
but the new "capability" package allows use to have a maintained list
of capabilities.

There's likely still some improvements to be made;

First of all, the capability package could provide a function to get the list
of strings.

On the completion-side, we need to consider what format is most convenient;
currently we use the canonical name (uppercase and "CAP_" prefix), however,
tab-completion is case-sensitive by default, so requires the user to type
uppercase letters to filter the list of options.

Bash completion provides a `completion-ignore-case on` option to make completion
case-insensitive (https://askubuntu.com/a/87066), but it looks to be a global
option; the current cobra.CompletionOptions also don't provide this as an option
to be used in the generated completion-script.

Fish completion has `smartcase` (by default?) which matches any case if
all of the input is lowercase.

Zsh does not have a dedicated option, but allows setting matching-rules
(see https://superuser.com/a/1092328).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 462e08219d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-17 23:08:14 +02:00
9e2d548799 Merge pull request #5530 from thaJeztah/27.x_backport_bump_deps
[27.x backport] vendor assorted dependencies in preparation of engine update
2024-10-14 14:36:53 +02:00
2668b11ce4 vendor: google.golang.org/grpc v1.66.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b6d27ff60e)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-14 13:31:11 +02:00
5cbb4ca191 vendor: google.golang.org/protobuf v1.34.1
full diff: https://github.com/protocolbuffers/protobuf-go/compare/v1.33.0...v1.34.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 200225f530)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-14 13:31:11 +02:00
78be4c464f vendor: github.com/cespare/xxhash/v2 v2.3.0
full diff: https://github.com/cespare/xxhash/compare/v2.2.0...v2.3.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9599251d07)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-14 13:31:11 +02:00
c93b0f1a0e vendor: golang.org/x/net v0.29.0
no changes in vendored code

full diff: https://github.com/golang/net/compare/v0.28.0...v0.29.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ea8aa2a419)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-14 13:31:11 +02:00
949859a39e vendor: golang.org/x/crypto v0.27.0
no changes in vendored code

full diff: https://github.com/golang/crypto/compare/v0.26.0...v0.27.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 61867feecf)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-14 13:31:10 +02:00
ae9b655aa8 vendor: golang.org/x/term v0.24.0
full diff: https://github.com/golang/term/compare/v0.23.0...v0.24.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 843ae6d7e2)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-14 13:31:10 +02:00
e93d817980 vendor: golang.org/x/text v0.18.0
no changes in vendored code

full diff: https://github.com/golang/text/compare/v0.17.0...v0.18.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit bea4ee6588)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-14 13:31:10 +02:00
ed7d30ef89 vendor: golang.org/x/sys v0.25.0
full diff: https://github.com/golang/sys/compare/v0.24.0...v0.25.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit a88ee33f71)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-14 13:31:10 +02:00
8437ca4e64 Merge pull request #5531 from thaJeztah/27.x_bump_engine_27.3.1
[27.x] vendor: github.com/docker/docker v27.3.1
2024-10-14 13:29:42 +02:00
3a1e08d6de vendor: github.com/docker/docker v27.3.1
no changes in code, as it's the same commit as v27.3.0

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-12 22:02:17 +02:00
cb3048fbeb Merge pull request #5519 from thaJeztah/27.x_backport_no_underline
[27.x backport] Do not underline image name
2024-10-08 17:09:05 +02:00
5721cdc76d Merge pull request #5518 from thaJeztah/27.x_backport_change_to_inuse
[27.x backport] Images Tree: Change 'Used' to 'In Use'
2024-10-08 17:08:38 +02:00
5c1cb99051 Merge pull request #5503 from austinvazquez/cherry-pick-9ecfe4f5a7634d9c375ffce1a273c421f2df716c-to-27.x
[27.x backport] move parsing key-value files to a separate package
2024-10-08 16:57:37 +02:00
9ab3d3a983 move parsing key-value files to a separate package
Move the code for parsing key-value files, such as used for
env-files and label-files to a separate package. This allows
other projects (such as compose) to use the same parsing
logic, but provide custom lookup functions for their situation
(which is slightly different).

The new package provides utilities for parsing key-value files
for either a file or an io.Reader. Most tests for EnvFile were
now testing functionality that's already tested in the new package,
so were (re)moved.

Co-authored-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9ecfe4f5a7)
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-10-08 14:18:58 +00:00
40539963c2 opts: parseKeyValueFile: cleanup and remove redundant trimming
- the function already trimmed leading whitespace from each line before
  parsing. keys with trailing whitespace would be invalidated, and values
  have whitespace preserved, so there's no need to trim whitespace for the
  key.
- if a line is validated (key is valid), we don't need to reconstruct the
  key=value by concatenating, and we can add the line as-is.
- check if the key is empty before checking if it contains whitespace
- touch-up comments
- rename some variables for readability
- slight cleanup to use early returns / early continues to reduce nesting

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 76196dbb01)
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-10-08 14:18:39 +00:00
25224ab615 opts: remove ErrBadKey as it's not used as a sentinel error
This error was originally introduced `ErrBadEnvVariable` in [moby/moby@500c8ba],
but merely for convenience, and not used as a sentinel error. After the code
was moved from the daemon to the cli repository, it was renamed to be more
generic `ErrBadKey` in commit 2b17f4c8a8.

A search on GitHub shows that there's no consumers using this error as
sentinel error, and it's not used in our own code as such, so it should
be safe to remove this error.

This patch removes the `ErrBadKey` error-type; it also removes the prefix
(`poorly formatted environment:`) to make the error more generic, because
the same function was used both for env-files and label-files.

[moby/moby@500c8ba]: 500c8ba4b6

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 95e221ef4d)
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-10-08 14:18:14 +00:00
4694fc8e45 Do not underline image name
Blue text with underline looks too much as a hyperlink I can click on

Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
(cherry picked from commit 17040890e4)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-08 16:00:38 +02:00
352ee0acd4 Images Tree: Change 'Used' to 'In Use'
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
(cherry picked from commit df52ddcfcc)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-08 15:39:06 +02:00
d0cb93ab08 Merge pull request #5512 from austinvazquez/cherry-pick-a6ab65948e6ecaf4167e2ef91b24c30929296256-to-27.x
[27.x backport] ci: update to go1.22.8
2024-10-08 10:38:46 +02:00
132ecbce8b ci: update to go1.22.8
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
(cherry picked from commit a6ab65948e)
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-10-08 03:54:21 +00:00
011cb195f0 Merge pull request #5509 from austinvazquez/cherry-pick-2f2b16a96687e50682497435a6de3b016ab1a5bc-to-27.x
[27.x backport] docs: fix inaccurate description of --restart=unless-stopped
2024-10-07 19:29:48 +02:00
6f5bfcb276 docs: fix inaccurate description of --restart=unless-stopped
`unless-stopped` containers are not restart after a daemon restart

Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 2f2b16a966)
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-10-07 16:37:35 +00:00
fa0bb9f4b4 Merge pull request #5470 from dvdksn/cp_5469
[27.x backport] use important callout for buildkit vs legacy builder
2024-10-04 13:06:17 +02:00
4c5eb4c301 Merge pull request #5499 from austinvazquez/cherry-pick-b129660dd39f566c6cdc692467a99bb422001a98-to-27.x
[27.x backport] opts: cleanup ParseEnvFile tests
2024-10-04 13:03:16 +02:00
b00668d4bc Merge pull request #5497 from austinvazquez/cherry-pick-6075303483f632f301e36c9dab3679949948df52-to-27.x
[27.x backport] docs/reference: stop, restart: add flag descriptions
2024-10-04 12:47:43 +02:00
e978602169 Merge pull request #5498 from austinvazquez/cherry-pick-54a20ce54caa3f24349eebd8f2cf6f47959c5556-to-27.x
[27.x backport] docs: fix a typo in run.md
2024-10-04 12:46:42 +02:00
19279c9e53 opts: cleanup ParseEnvFile tests
- Use gotest.tools for assertions
- Check for expected error messages
- Don't check for ErrBadKey errors, as it's not used
  as a sentinel error anywhere.
- Use t.SetEnv() instead of depending on `HOME` being set
- Use t.TempDir() for writing temporary files

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b129660dd3)
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-10-04 06:11:46 +00:00
d2aec10148 docs: fix a typo in run.md
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 54a20ce54c)
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-10-04 06:07:07 +00:00
6445ae2f02 docs/reference: stop, restart: add flag descriptions
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 6075303483)
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-10-04 06:01:26 +00:00
5771be8156 Merge pull request #5472 from dvdksn/cp_5471
[27.x backport] fix anchor link to web-based login section
2024-09-23 14:16:53 +01:00
fbd51c01c6 docs: fix anchor link to web-based login section
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 465e87afc7)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-09-23 13:53:48 +02:00
23cea90233 docs: use important callout for buildkit vs legacy builder
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 8a3d838a19)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-09-23 13:40:54 +02:00
ce1223035a Merge pull request #5462 from thaJeztah/27.x_backport_bump_compose
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x] Dockerfile: update compose to v2.29.7
2024-09-20 12:01:47 +01:00
263ba959c4 Merge pull request #5461 from laurazard/27.x-backport-update-VERSION 2024-09-20 11:48:12 +01:00
be9b9f3d70 Update VERSION file to v27.3.1-dev
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit 91c90a9797)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-09-20 11:41:57 +01:00
a4149b0c3d Dockerfile: update compose to v2.29.7
Update the compose cli plugin used in the dev-container

full diff: https://github.com/docker/compose/compare/v2.29.4...v2.29.7

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ce26ebc0e9)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-20 12:41:56 +02:00
4aac4154b0 Merge pull request #5458 from thaJeztah/27.x_bump_engine3
[27.x] vendor: github.com/docker/docker v27.3.0
2024-09-20 11:23:44 +01:00
854695884a vendor: github.com/docker/docker v27.3.0
no diff, just re-tagged; https://github.com/docker/docker/compare/v27.3.0-rc.2...v27.3.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-20 12:13:41 +02:00
f05200311d Merge pull request #5457 from laurazard/backport-dropped-defer
[27.x backport] telemetry: fix early meterprovider shutdown
2024-09-20 01:49:45 +01:00
460f1becc5 telemetry: fix early meterprovider shutdown
In 4b5a196fee, we changed the CLI global
meter provider shutdown in order to handle any error returned by the
metric export.

Unfortunately, we dropped a `defer` during the fix, which
causes the meter provider to be immediately shutdown after being created
and metrics to not be collected/exporter.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit 1355d7e9f8)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-09-20 01:37:00 +01:00
e85edf8556 Merge pull request #5452 from laurazard/27.3.0-match-moby-version
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x] vendor: github.com/docker/docker v27.3.0-rc2
2024-09-18 17:59:19 +02:00
ca62759b0f vendor: github.com/docker/docker v27.3.0-rc2
No vendor changes, just using tag.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-09-18 16:30:22 +01:00
e4dc9d2ac7 Merge pull request #5448 from laurazard/backport-otel-shutdown-errors
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x backport] telemetry: handle otel errors on shutdown
2024-09-18 12:20:40 +02:00
ddf8f23403 Merge pull request #5449 from laurazard/backport-lowercase-windows-drive
[27.x backport] command: change drive to lowercase for wsl path
2024-09-18 12:16:32 +02:00
c6dde25470 command: change drive to lowercase for wsl path
On Windows, the drive casing doesn't matter outside of WSL. For WSL, the
drives are lowercase. When we're producing a WSL path, lowercase the
drive letter.

Co-authored-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
Co-authored-by: Laura Brehm <laurabrehm@hey.com>

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit 3472bbc28a)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-09-18 11:05:51 +01:00
fcf6dd05df Merge pull request #5447 from thaJeztah/27.x_backport_codeql_updates
[27.x backport] gha: update codeql workflow to go1.22.7
2024-09-18 11:04:09 +01:00
4b5a196fee telemetry: pass otel errors to the otel handler for shutdown and force flush
Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
(cherry picked from commit b1956f5073)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-09-18 10:43:32 +01:00
e2eb069aed gha: update codeql workflow to go1.22.7
commit d7d56599ca updated this
repository to go1.22, but the codeql action didn't specify a
patch version, and was missed.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit e1213edcc6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-17 21:58:05 +02:00
58a14cc379 Merge pull request #5443 from laurazard/backport-container-rm-docs
[27.x backport] docs: Fix --rm=false flag in container_run.md
2024-09-16 14:49:01 +01:00
81ca58bd20 docs: Fix --rm=false flag in container_run.md
Signed-off-by: Julio Cesar Garcia <juliogarciamelgarejo@gmail.com>
(cherry picked from commit 605c9bf160)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-09-16 14:31:37 +01:00
6b5e4be0cb Merge pull request #5442 from thaJeztah/27.x_backport_bump_buildx_compose
[27.x backport] Dockerfile: update buildx to v0.17.1, compose to v2.29.4
2024-09-16 14:27:43 +01:00
b5c6541769 Dockerfile: update compose to v2.29.4
Update the compose cli plugin used in the dev-container

full diff: https://github.com/docker/compose/compare/v2.29.0...v2.29.4

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit f7a513cff0)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-16 15:05:04 +02:00
6fdd11b012 Dockerfile: update buildx to v0.17.1
Update the buildx cli plugin used in the dev-container

full diff: https://github.com/docker/buildx/compare/0.16.1...0.17.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 42ce06aa5b)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-16 15:05:04 +02:00
6bf64a88af Merge pull request #5440 from thaJeztah/27.x_vendor_moby
[27.x] vendor: github.com/docker/docker v27.3.0-rc.1
2024-09-16 13:41:37 +01:00
d896559894 vendor: github.com/docker/docker v27.3.0-rc.1
no diff, as it's the same commit, but tagged

full diff: https://github.com/docker/docker/compare/bf60e5cced83...v27.3.0-rc.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-16 14:28:09 +02:00
f350724e21 Merge pull request #5439 from thaJeztah/27.x_backport_docs_updates
[27.x backport] docs, man: dockerd: add documentation for "--log-format" and "--feature" options
2024-09-16 14:24:39 +02:00
84d4a5ef11 docs: dockerd: add documentation for --log-format option
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit baceb4b158)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-16 13:55:17 +02:00
7c72283b37 man: dockerd: add description for --log-format option
This option was added in a08abec9f8d59eaa44c375900e254384a68c5a31,
as part of Docker v25.0, but did not update the docs and manpage.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9ae514fdc7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-16 13:55:17 +02:00
245f79ff57 docs/reference: dockerd: add docs for --feature option
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit a42ca1148d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-16 13:55:03 +02:00
db7a014d37 man: dockerd: value is optional for --feature flag
The --feature flag allows the boolean value to be omitted.
If only a name is provided, the default is "true".

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit a357db0aba)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-16 13:55:03 +02:00
bc87ef962a man: fix duplicate word in --feature flag description
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit fb056d2ceb)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-16 13:55:03 +02:00
7811c442d6 man: update dockerd man-page to include --feature flag
commit f13c08246d93dd5aae200d5881a3a374e6cac876 introduced
this flag, but did not yet update the manpage.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 610f9157f5)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-16 13:55:00 +02:00
48a2cdff97 Merge pull request #5432 from thaJeztah/27.x_backport_wsl-socket-path
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x backport] command: check for wsl mount path on windows
2024-09-13 10:54:31 +02:00
b48720e65e command: check for wsl mount path on windows
This checks for the equivalent WSL mount path on windows. WSL will mount
the windows drives at `/mnt/c` (or whichever drive is being used).

This is done by parsing a UNC path with forward slashes from the unix
socket URL.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
(cherry picked from commit 38c3fef1a8)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-13 00:35:37 +02:00
c85c3df6b5 Merge pull request #5431 from thaJeztah/27.x_bump_engine2
[27.x] vendor: github.com/docker/docker bf60e5cced83 (v27.3.0-dev)
2024-09-13 00:34:34 +02:00
74bebe2719 vendor: github.com/docker/docker bf60e5cced83 (v27.3.0-dev)
full diff: https://github.com/docker/docker/compare/v27.2.1...bf60e5cced830ee3034275c1792095fbfa40caf4

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-13 00:21:52 +02:00
9748bef1c3 Merge pull request #5430 from thaJeztah/27.x_bump_engine_deps
[27.x backport] update dependencies for engine update
2024-09-13 00:21:32 +02:00
2fc18b9874 vendor: google.golang.org/grpc v1.62.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit dccb8bfa5d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:59:44 +02:00
1a199b63da vendor: tags.cncf.io/container-device-interface v0.8.0
Breaking change: The .ToOCI() functions in the specs-go package have been
removed. This removes the dependency on the OCI runtime specification from
the CDI specification definition itself.

What's Changed

- Add workflow to mark prs and issues as stale
- Remove the ToOCI functions from the specs-go package
- docs: add a pointer to community meetings in our docs.
- Bump spec version to v0.8.0
- Update spec version in README

Full diff: https://github.com/cncf-tags/container-device-interface/compare/v0.7.2...v0.8.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 8cdf90cd93)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:56:14 +02:00
968341cc7d vendor: golang.org/x/net v0.28.0
full diff: https://github.com/golang/net/compare/v0.25.0...v0.28.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit a5f15bee7a)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:56:14 +02:00
fbb0cfd86a vendor: golang.org/x/crypto v0.26.0
full diff: https://github.com/golang/crypto/compare/v0.23.0...v0.26.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b93fc39639)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:56:13 +02:00
49e33a03df vendor: golang.org/x/text v0.17.0
full diff: https://github.com/golang/text/compare/v0.15.0...v0.17.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 3a63df265f)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:56:13 +02:00
295b75e5ff vendor: golang.org/x/term v0.23.0
full diff: https://github.com/golang/term/compare/v0.20.0...v0.23.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit c6e5341934)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:56:13 +02:00
090d1ff555 vendor: golang.org/x/time v0.6.0
full diff: https://github.com/golang/time/compare/v0.3.0...v0.6.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 5f9fe33b6b)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:56:13 +02:00
5dde3d8570 vendor: golang.org/x/sync v0.8.0
full diff: https://github.com/golang/sync/compare/v0.7.0...v0.8.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 7074e5011f)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:56:13 +02:00
d64740d347 vendor: golang.org/x/sys v0.24.0
full diff: https://github.com/golang/sys/compare/v0.22.0...v0.24.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 958fff82f1)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:56:12 +02:00
f68936ef2e vendor: dario.cat/mergo v1.0.1
- fix: overwriteWithEmptyValue is forced to true when merging an object
  involving maps
- fix: WithoutDereference should respect non-nil struct pointers

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit fb264ffc08)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:56:12 +02:00
4607c883c5 vendor: github.com/moby/sys/sequential v0.6.0
full diff: https://github.com/moby/sys/compare/sequential/v0.5.0...sequential/v0.6.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b34e8e4dff)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:53:02 +02:00
7ff3daa446 vendor: github.com/moby/sys/symlink v0.3.0
full diff: https://github.com/moby/sys/compare/symlink/v0.2.0...symlink/v0.3.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ea37ac9bac)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:52:59 +02:00
1fe06dd0e7 vendor: github.com/moby/sys/signal v0.7.1
full diff: https://github.com/moby/sys/compare/signal/v0.7.0...signal/v0.7.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 435c658333)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 23:47:30 +02:00
b381ab1d8d Merge pull request #5429 from thaJeztah/27.x_bump_docker
[27.x] vendor: github.com/docker/docker v27.2.1
2024-09-12 22:42:13 +02:00
05fbfc6995 vendor: github.com/docker/docker v27.2.1
no diff: same commit, but tagged

full diff: https://github.com/docker/docker/compare/8b539b8df240...v27.2.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-12 22:16:13 +02:00
fba240c5b4 Merge pull request #5426 from thaJeztah/27.x_backport_fix-panic-volume-update
[27.x backport] volume/update: require 1 argument/fix panic
2024-09-11 15:24:41 +01:00
965699ba0f cli/command/volume TestUpdateCmd: adjust for older error messages
The error-message changed in newer versions, and no longer includes
"exactly".

This patch adjusts the test in the meantime.

    59.13 === FAIL: cli/command/volume TestUpdateCmd (0.00s)
    59.13     update_test.go:21: assertion failed: expected error to contain "requires 1 argument", got "\"update\" requires exactly 1 argument.\nSee 'update --help'.\n\nUsage:  update [OPTIONS] [VOLUME] [flags]\n\nUpdate a volume (cluster volumes only)"

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-11 13:26:06 +02:00
c65ac2d90d volume/update: require 1 argument/fix panic
This command was declaring that it requires at least 1 argument, when it
needs exactly 1 argument. This was causing the CLI to panic when the
command was invoked with no argument:

`docker volume update`

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit daea277ee8)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-11 13:12:54 +02:00
05fb576772 Merge pull request #5423 from thaJeztah/27.x_backport_info_no_expected_version
[27.x backport] info: stop printing "Expected" commits
2024-09-10 19:51:58 +02:00
4be6b1f3d7 info: stop printing "Expected" commits
The `Commit` type was introduced in 2790ac68b3,
to assist triaging issues that were reported with an incorrect version of
runc or containerd. At the time, both `runc` and `containerd` were not yet
stable, and had to be built from a specific commit to guarantee compatibility.

We encountered various situations where unexpected (and incompatible) versions
of those binaries were packaged, resulting in hard to trace bug-reports.
For those situations, a "expected" version was set at compile time, to
indicate if the version installed was different from the expected version;

    docker info
    ...
    runc version: a592beb5bc4c4092b1b1bac971afed27687340c5 (expected: 69663f0bd4b60df09991c08812a60108003fa340)

Both `runc` and `containerd` are stable now, and docker 19.03 and up set the
expected version to the actual version since c65f0bd13c
and 23.0 did the same for the `init` binary b585c64e2b,
to prevent the CLI from reporting "unexpected version".

In short; the `Expected` fields no longer serves a real purpose, so we should
no longer print it.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 88ca4e958f)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-10 16:41:43 +02:00
65decb5731 Merge pull request #5419 from dvdksn/bp_5403
[27.x backport] rename plugins index file and add linkTitle
2024-09-09 11:05:09 +02:00
90559a6143 chore: fix style/lint issues in deprecated.md
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 0fcaffb7e4)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-09-09 10:54:29 +02:00
dbde5b3681 docs: add front matter title to deprecated.md
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 5ca40e0a35)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-09-09 10:33:23 +02:00
b1e50eea92 docs: rename plugins index file and add linkTitle
We publish this page on docs.docker.com, and hugo expects index pages
for sections to be named _index.md. We currently rename the page when we
mount it to the docs repo but might as well change the filename in the
source.

Also adds a linkTitle to the page, which is a shorter title that will be
used in the sidebar navigation and breadcrumbs.

Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 071f6f9391)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-09-09 10:33:23 +02:00
9e34c9bb39 Merge pull request #5414 from vvoland/vendor-docker
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x] vendor: github.com/docker/docker v27.2.1-dev (8b539b8df240)
2024-09-06 12:01:30 +00:00
324cdbca40 vendor: github.com/docker/docker v27.2.1-dev (8b539b8df240)
full diff: https://github.com/docker/docker/compare/v27.2.0...8b539b8df240

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-09-06 13:48:24 +02:00
b5290d4e0b Merge pull request #5411 from vvoland/5410-27.x
[27.x backport] update to go1.22.7
2024-09-06 10:28:04 +02:00
3db9538748 update to go1.22.7
- https://github.com/golang/go/issues?q=milestone%3AGo1.22.7+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.22.6...go1.22.7

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

- go/parser: stack exhaustion in all Parse* functions

    Calling any of the Parse functions on Go source code which contains deeply nested literals can cause a panic due to stack exhaustion.

    This is CVE-2024-34155 and Go issue https://go.dev/issue/69138.

- encoding/gob: stack exhaustion in Decoder.Decode

    Calling Decoder.Decode on a message which contains deeply nested structures can cause a panic due to stack exhaustion.

    This is a follow-up to CVE-2022-30635.

    Thanks to Md Sakib Anwar of The Ohio State University (anwar.40@osu.edu) for reporting this issue.

    This is CVE-2024-34156 and Go issue https://go.dev/issue/69139.

- go/build/constraint: stack exhaustion in Parse

    Calling Parse on a "// +build" build tag line with deeply nested expressions can cause a panic due to stack exhaustion.

    This is CVE-2024-34158 and Go issue https://go.dev/issue/69141.

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

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 3bf39d25a0)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-09-05 17:53:21 +02:00
1ab89e71fa Merge pull request #5409 from thaJeztah/27.x_update_docker
[27.x] vendor: github.com/docker/docker v27.2.0
2024-09-05 15:02:51 +02:00
667d9fd4df Merge pull request #5408 from thaJeztah/27.x_backport_mod_tidy
[27.x backport] vendor.mod: put github.com/pkg/browser in the right group
2024-09-05 14:53:18 +02:00
41e61c45d9 [27.x] vendor: github.com/docker/docker v27.2.0
Use a tagged version instead of the commit. No diff as they are the same;
https://github.com/docker/docker/compare/3ab5c7d0036c...v27.2.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-05 14:45:28 +02:00
869df10064 vendor.mod: put github.com/pkg/browser in the right group
commit fcfdd7b91f introduced github.com/pkg/browser
as a direct dependency, but it ended up in the group for indirect dependencies.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 1b8180a405)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-05 14:40:13 +02:00
6feee4ab35 Merge pull request #5402 from laurazard/backport-27.x-login-not-interactive
[27.x backport] login: handle non-tty scenario consistently
2024-09-03 14:45:27 +00:00
d0c1a80617 login: handle non-tty scenario consistently
Running `docker login` in a non-interactive environment sometimes errors
out if no username/pwd is provided. This handling is somewhat
inconsistent – this commit addresses that.

Before:
| `--username` | `--password` | Result                                                             |
|:------------:|:------------:| ------------------------------------------------------------------ |
|            |            |                                                                  |
|            |            | `Error: Cannot perform an interactive login from a non TTY device` |
|            |            | `Error: Cannot perform an interactive login from a non TTY device` |
|            |            | hangs                                                              |

After:
| `--username` | `--password` | Result                                                             |
|:------------:|:------------:| ------------------------------------------------------------------ |
|            |            |                                                                  |
|            |            | `Error: Cannot perform an interactive login from a non TTY device` |
|            |            | `Error: Cannot perform an interactive login from a non TTY device` |
|            |            | `Error: Cannot perform an interactive login from a non TTY device` |

It's worth calling out a separate scenario – if there are previous,
valid credentials, then running `docker login` with no username or
password provided will use the previously stored credentials, and not
error out.

```console
cat ~/.docker/config.json
{
        "auths": {
                "https://index.docker.io/v1/": {
                        "auth": "xxxxxxxxxxx"
                }
        }
}
⭑ docker login 0>/dev/null
Authenticating with existing credentials...

Login Succeeded
```

This commit also applies the same non-interactive handling logic to the
new web-based login flow, which means that now, if there are no prior
credentials stored and a user runs `docker login`, instead of initiating
the new web-based login flow, an error is returned.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit bbb6e7643d)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-09-03 15:39:17 +01:00
383c428451 Merge pull request #5400 from vvoland/5387-27.x
[27.x backport] update to go1.22.6
2024-09-03 14:04:04 +02:00
5f8416e541 Merge pull request #5399 from vvoland/5376-27.x
[27.x backport] oauth/api: drain timer channel on each iteration
2024-09-03 14:02:04 +02:00
5bf5cb9ff6 update to go1.22.6
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit d7d56599ca)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-09-03 13:52:28 +02:00
3a15d5a640 Merge pull request #5395 from thaJeztah/27.x_backport_fix_plugins_CGO_ENABLED
[27.x backport] scripts/build/plugins: don't override CGO_ENABLED set by .variables
2024-09-03 13:25:44 +02:00
1dfd11acc0 oauth/api: drain timer channel on each iteration
Previously, if while polling for oauth device-code login results a user
suspended the process (such as with CTRL-Z) and then restored it with
`fg`, an error might occur in the form of:

```
failed waiting for authentication: You are polling faster than the specified interval of 5 seconds.
```

This is due to our use of a `time.Ticker` here - if no receiver drains
the ticker channel (and timers/tickers use a buffered channel behind the
scenes), more than one tick will pile up, causing the program to "tick"
twice, in fast succession, after it is resumed.

The new implementation replaces the `time.Ticker` with a `time.Timer`
(`time.Ticker` is just a nice wrapper) and introduces a helper function
`resetTimer` to ensure that before every `select`, the timer is stopped
and it's channel is drained.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit 60d0450287)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-09-03 13:16:03 +02:00
de2b49b074 scripts/build/plugins: don't override CGO_ENABLED set by .variables
The `.variables` sets `CGO_ENABLED=1` on arm; b0c41b78d8/scripts/build/.variables (L57-L68)
And if enabled, it sets `-buildmode=pie`; b0c41b78d8/scripts/build/.variables (L79-L88)

But that looks to be conflicting with the hardcoded `CGO_ENABLED=0` in
this script, which causes the build to fail on go1.22;

    > [build-plugins 1/1] RUN --mount=ro --mount=type=cache,target=/root/.cache     xx-go --wrap &&     TARGET=/out ./scripts/build/plugins e2e/cli-plugins/plugins/*:
    0.127 Building static docker-helloworld
    0.127 + CGO_ENABLED=0
    0.127 + GO111MODULE=auto
    0.127 + go build -o /out/plugins-linux-arm/docker-helloworld -tags ' osusergo' -ldflags ' -X "github.com/docker/cli/cli/version.GitCommit=5c123b1" -X "github.com/docker/cli/cli/version.BuildTime=2024-09-02T13:52:17Z" -X "github.com/docker/cli/cli/version.Version=pr-5387" -extldflags -static' -buildmode=pie github.com/docker/cli/cli-plugins/examples/helloworld
    0.135 -buildmode=pie requires external (cgo) linking, but cgo is not enabled

This patch sets the CGO_ENABLED variable before sourcing `.variables`,
so that other variables which are conditionally set are handled correctly.

Before this PR:

    #18 [build-plugins 1/1] RUN --mount=ro --mount=type=cache,target=/root/.cache     xx-go --wrap &&     TARGET=/out ./scripts/build/plugins e2e/cli-plugins/plugins/*
    #18 0.123 Building static docker-helloworld
    #18 0.124 + CGO_ENABLED=0
    #18 0.124 + GO111MODULE=auto
    #18 0.124 + go build -o /out/plugins-linux-arm/docker-helloworld -tags ' osusergo' -ldflags ' -X "github.com/docker/cli/cli/version.GitCommit=c8c402e" -X "github.com/docker/cli/cli/version.BuildTime=2024-09-03T08:28:25Z" -X "github.com/docker/cli/cli/version.Version=pr-5381" -extldflags -static' -buildmode=pie github.com/docker/cli/cli-plugins/examples/helloworld
    ....

With this PR:

    #18 [build-plugins 1/1] RUN --mount=ro --mount=type=cache,target=/root/.cache     xx-go --wrap &&     TARGET=/out ./scripts/build/plugins e2e/cli-plugins/plugins/*
    #18 0.110 Building static docker-helloworld
    #18 0.110 + GO111MODULE=auto
    #18 0.110 + go build -o /out/plugins-linux-arm/docker-helloworld -tags '' -ldflags ' -X "github.com/docker/cli/cli/version.GitCommit=050d9d6" -X "github.com/docker/cli/cli/version.BuildTime=2024-09-03T09:19:05Z" -X "github.com/docker/cli/cli/version.Version=pr-5387"' github.com/docker/cli/cli-plugins/examples/helloworld
    ....

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9e29967960)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-09-03 12:56:19 +02:00
c5d846735c Merge pull request #5394 from dvdksn/bp_5386
[27.x backport] docs: update docker login reference #5386
2024-09-03 12:55:19 +02:00
6274754e66 copynit: s/WEB BASED/WEB-BASED/
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 81744d7aa8)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-09-03 12:22:57 +02:00
7a50cd0f01 docs: update docker login reference
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 2f206fff3c)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-09-03 12:22:52 +02:00
074dfc0f88 Merge pull request #5392 from vvoland/5345-27.x
[27.x backport] cli/connhelper: getConnectionHelper: move ssh-option funcs out of closure
2024-09-02 22:29:25 +02:00
92423287cc Merge pull request #5391 from vvoland/5389-27.x
[27.x backport] Dockerfile: update xx to v1.5.0
2024-09-02 22:27:57 +02:00
1a0b6a7a44 cli/connhelper: getConnectionHelper: move ssh-option funcs out of closure
The addSSHTimeout and disablePseudoTerminalAllocation were added in commits
a5ebe2282a and f3c2c26b10,
and called inside the Dialer function, which means they're called every
time the Dialer is called. Given that the sshFlags slice is not mutated
by the Dialer, we can call these functions once.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 0fd3fb0840)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-09-02 20:57:42 +02:00
8fcfc0b803 Dockerfile: update xx to v1.5.0
full diff: https://github.com/tonistiigi/xx/compare/v1.4.0...v1.5.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 1e6cbbc3f1)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-09-02 20:55:31 +02:00
28d2fed463 Merge pull request #5385 from vvoland/5383-27.x
[27.x backport] login: use normalized hostname when storing
2024-09-02 10:57:51 +01:00
83072c0232 login: use normalized hostname when storing
Normalization/converting the registry address to just a hostname happens
inside of `command.GetDefaultAuthConfig`. Use this value for the rest of
the login flow/storage.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit e532eead91)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-09-02 11:43:26 +02:00
40109aa45f Merge pull request #5380 from laurazard/dont-normalize-registry-1-backport
[27.x backport] Revert "login: normalize `registry-1.docker.io`"
2024-08-29 13:33:58 +02:00
32aadc9902 Revert "login: normalize registry-1.docker.io"
This reverts commit e6624676e0.

Since e6624676e0, during login, we started
normalizing `registry-1.docker.io` to `index.docker.io`. This means that
if a user logs in with `docker login -u [username]
registry-1.docker.io`, the user's credentials get stored in
credhelpers/config.json under `https://index.docker.io/v1/`.

However, while the registry code normalizes an image reference without
registry (`docker pull alpine:latest`) and image references explicitly for
`index.docker.io` (`docker pull index.docker.io/library/alpine:latest`)
to the official index server (`https://index.docker.io/v1/`), and
fetches credentials for that auth key, it does not normalize
`registry-1.docker.io`, which means pulling explicitly from there
(`docker pull registry-1.docker.io/alpine:latest`) will not use
credentials stored under `https://index.docker.io/v1/`.

As such, until changes are made to the registry/pull/push code to
normalize `registry-1.docker.io` to `https://index.docker.io/v1/`, we
should not normalize this during login.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit dab9674db9)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-08-29 12:23:27 +01:00
3ab4256958 Merge pull request #5374 from vvoland/vendor-docker
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x backport] vendor: github.com/docker/docker 3ab5c7d0036c (v27.2.0-dev)
2024-08-27 16:08:11 +02:00
88a49df297 vendor: github.com/docker/docker 3ab5c7d0036c (v27.2.0-dev)
full diff: b27de4ef16...3ab5c7d003

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-27 16:02:26 +02:00
5d17c29eb2 Merge pull request #5372 from thaJeztah/27.x_backport_fix_linting_issues
[27.x backport] Fix linting issues in preparation of Go and GolangCI-lint update
2024-08-26 17:06:00 +02:00
64b9e4cd16 cli: rename args that collided with builtins (predeclard)
cli/required.go:33:22: param min has same name as predeclared identifier (predeclared)
    func RequiresMinArgs(min int) cobra.PositionalArgs {
                         ^
    cli/required.go:50:22: param max has same name as predeclared identifier (predeclared)
    func RequiresMaxArgs(max int) cobra.PositionalArgs {
                         ^
    cli/required.go:67:24: param min has same name as predeclared identifier (predeclared)
    func RequiresRangeArgs(min int, max int) cobra.PositionalArgs {
                           ^

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit c4a55df7c0)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-08-26 14:49:10 +02:00
4b71d0d1af e2e/global: fix n-constant format string in call (govet)
e2e/global/cli_test.go:217:28: printf: non-constant format string in call to gotest.tools/v3/poll.Continue (govet)
                            return poll.Continue(err.Error())
                                                 ^

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9c87891278)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-08-26 14:38:40 +02:00
002cfcde85 cli/command: fix n-constant format string in call (govet)
cli/command/utils.go:225:29: printf: non-constant format string in call to github.com/pkg/errors.Wrapf (govet)
                return errors.Wrapf(err, fmt.Sprintf("invalid output path: %q must be a directory or a regular file", path))
                                         ^
    cli/command/manifest/cmd.go:21:33: printf: non-constant format string in call to fmt.Fprintf (govet)
                fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
                                             ^
    cli/command/service/remove.go:45:24: printf: non-constant format string in call to github.com/pkg/errors.Errorf (govet)
            return errors.Errorf(strings.Join(errs, "\n"))
                                 ^
    cli/command/service/scale.go:93:23: printf: non-constant format string in call to github.com/pkg/errors.Errorf (govet)
        return errors.Errorf(strings.Join(errs, "\n"))
                             ^
    cli/command/stack/swarm/remove.go:74:24: printf: non-constant format string in call to github.com/pkg/errors.Errorf (govet)
            return errors.Errorf(strings.Join(errs, "\n"))
                                 ^

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit f101f07a7b)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-08-26 14:38:40 +02:00
d8af7812b5 cli/command/system: remove redundant nil-check (gosimple)
cli/command/system/info.go:375:5: S1009: should omit nil check; len() for []github.com/docker/docker/api/types/system.NetworkAddressPool is defined as zero (gosimple)
        if info.DefaultAddressPools != nil && len(info.DefaultAddressPools) > 0 {
           ^

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit cc1d7b7ac9)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-08-26 14:38:37 +02:00
f042ddb5c9 Merge pull request #5371 from vvoland/vendor-docker
vendor: github.com/docker/docker b27de4ef1634 (v27.2.0-dev)
2024-08-26 14:33:00 +02:00
8e94ed15e6 vendor: github.com/docker/docker b27de4ef1634 (v27.2.0-dev)
full diff: 9942d656ba...b27de4ef16

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-26 14:02:54 +02:00
7a82aeeeba Merge pull request #5368 from dvdksn/27x_5360
[27.x backport] update link to engine api reference
2024-08-22 18:01:29 +02:00
24837f9260 chore: update link to docker engine api reference
Engine API reference page is moving to /reference/api/engine

Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit c974a83391)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-08-22 15:18:55 +02:00
5805df0205 Merge pull request #5365 from vvoland/5363-27.x
[27.x backport] cli/formatter: bracket IPv6 addrs prepended to ports
2024-08-22 14:23:10 +02:00
fb20f009f7 Merge pull request #5366 from dvdksn/27x_f1befabe9f1c979d94c39eeb7020e106b3c1e6a6
[27.x backport] use gh alert syntax for callouts
2024-08-21 15:22:02 +02:00
6ceb0aba82 cli/formatter: bracket IPv6 addrs prepended to ports
On `docker ps`, port bindings with an IPv6 HostIP should have their
addresses put into brackets when joining them to their ports.

RFC 3986 (Section 3.2.2) stipulates that IPv6 addresses should be
enclosed within square brackets. This RFC is only about URIs. However,
doing so here helps user identifier what's part of the IP address and
what's the port. It also makes it easier to copy/paste that
'[addr]:port' into other software (including browsers).

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
(cherry picked from commit 964155cd27)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-21 11:45:56 +02:00
2d7b8998c4 docs: use gh alert syntax for callouts
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit f1befabe9f)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-08-21 11:41:15 +02:00
cabd410a1a Merge pull request #5362 from laurazard/27.x-backport-oauth-escape-hatch
[27.x backport] login: add oauth escape hatch
2024-08-20 16:11:01 +02:00
a58af379e1 login: add e2e tests for oauth + escape hatch
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit a327476f7f)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-08-20 12:50:57 +01:00
1b3fa65759 login: add oauth escape hatch
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit 846ecf59ff)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-08-20 12:50:49 +01:00
cf01923519 Merge pull request #5348 from dvdksn/backport_update_build_context_link
[27.x backport] docs: update link to moved build context doc
2024-08-19 17:48:55 +02:00
a0d7f0dbd3 Merge pull request #5358 from vvoland/5356-27.x
[27.x backport] list/tree: No extra spacing for graphdriver
2024-08-19 13:47:05 +02:00
0c4e7478e2 list/tree: No extra spacing for graphdriver
Don't output the extra spacing around the images when none of the
top-level image entries has any children.

This makes the list look better when ran against the graphdrivers image
store.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 7b91647943)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-19 13:28:13 +02:00
60ce3fbc96 Merge pull request #5353 from vvoland/4982-27.x
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x backport] image/list: Add `--tree` flag
2024-08-16 19:51:04 +02:00
7902b52714 list/tree: Print <untagged> as dangling image name
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 351249dce9)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-16 19:40:38 +02:00
7196200fc2 list/tree: Fix some escape codes included in nonTTY
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 6979ab073c)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-16 19:40:36 +02:00
f42fa0b8e1 list/tree: Add spacing before the content and first image
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit a9b78da546)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-16 19:40:34 +02:00
b719b10257 list/tree: Capitalize column headers
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 0242a1e3c6)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-16 19:40:33 +02:00
ab55d75cf5 list/tree: Add an experimental warning
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit d417d06682)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-16 19:40:32 +02:00
324cc5d30f list/tree: Sort by created date
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit b1a08f7841)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-16 19:40:30 +02:00
44a9ffa0ad list/tree: Align number right, text left
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 18ab78882c)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-16 19:40:29 +02:00
ba43ae0bd2 cli/tree: Add Content size column
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit ea8aafcd9e)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-16 19:40:28 +02:00
99b647cfca image/list: Add --tree flag
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit be11b74ee9)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-16 19:40:26 +02:00
f90dc28f1e Merge pull request #5354 from vvoland/vendor-docker
[27.x] vendor: github.com/docker/docker v27.2.0-dev (9942d656bade)
2024-08-16 19:39:57 +02:00
26536d1145 vendor: github.com/docker/docker v27.2.0-dev (9942d656bade)
full diff: f9522e5e96...9942d656ba

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-16 18:59:59 +02:00
c5e733becc Merge pull request #5349 from laurazard/27.x-backport-oauth-login
[27.x backport] auth: add support for oauth device-code login
2024-08-16 18:13:24 +02:00
7227402d94 Merge pull request #5351 from laurazard/backport-27.x-disable-pseudoterminal-ssh
[27.x backport] disable pseudoterminal creation
2024-08-16 18:12:10 +02:00
83f6ca4a73 disable pseudoterminal creation
avoided the join, also did manual iteration

added test, also added reflect for the DeepEqual comparison

Signed-off-by: Archimedes Trajano <developer@trajano.net>
(cherry picked from commit f3c2c26b10)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-08-16 14:11:10 +01:00
ad7912a846 fallback to regular login if oauth login fails to start
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit c3fe7bc336)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-08-16 10:09:41 +01:00
afb5e143b1 login: normalize registry-1.docker.io
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit e6624676e0)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-08-16 10:09:40 +01:00
b8a38fd22d Refactor cli/command/registry
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit 6e4818e7d6)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-08-16 10:09:39 +01:00
0c29d6bac1 auth: add support for oauth device-code login
This commit adds support for the oauth [device-code](https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow)
login flow when authenticating against the official registry.

This is achieved by adding `cli/internal/oauth`, which contains code to manage
interacting with the Docker OAuth tenant (`login.docker.com`), including launching
the device-code flow, refreshing access using the refresh-token, and logging out.

The `OAuthManager` introduced here is also made available through the `command.Cli`
interface method `OAuthManager()`.

In order to maintain compatibility with any clients manually accessing
the credentials through `~/.docker/config.json` or via credential
helpers, the added `OAuthManager` uses the retrieved access token to
automatically generate a PAT with Hub, and store that in the
credentials.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit fcfdd7b91f)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-08-16 10:09:38 +01:00
3eaf30278f docs: update link to moved build context doc
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 2dd4eb06ae)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-08-13 12:17:58 +02:00
d01f264bcc Merge pull request #5333 from thaJeztah/27.x_bump_engine
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.x] vendor: github.com/docker/docker f9522e5e96c3 (v27.1.2-dev) (removes containerd dependency)
2024-08-12 13:34:32 +02:00
65dec14ac0 vendor: github.com/docker/docker f9522e5e96c3 (v27.1.2-dev)
Removes dependency on containerd, as the userns package was migrated
to the github.com/moby/sys/userns module.

- full diff: https://github.com/docker/docker/compare/v27.1.1...f9522e5e96c3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-12 13:18:02 +02:00
1f80c54b51 Merge pull request #5339 from thaJeztah/27.x_backport_fix_bps_limit
[27.x backport] run: fix GetList return empty issue for throttledevice
2024-08-12 11:57:35 +02:00
33573e20bc Merge pull request #5343 from dvdksn/cp-docs-manuals-refactor-linkfix
[27.x backport] cherry-pick doc linkfixes due to refactor
2024-08-12 10:11:43 +02:00
73452e316f docs: update internal links after refactor
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit d4a362aa1c)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-08-11 16:58:39 +02:00
bcd90be73a docs: fix link to http proxy document
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 78a8fba2cc)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-08-11 16:46:02 +02:00
f62c68eedd Merge pull request #5337 from vvoland/5327-27.x
[27.x backport] plugins: don't panic on Close if PluginServer nil
2024-08-09 20:03:37 +02:00
946d1097b8 run: fix GetList return empty issue for throttledevice
Test "--device-read-bps" "--device-write-bps" will fail. The root
cause is that GetList helper return empty as its local variable
initialized to zero size.

This patch fix it by setting the related slice size to non-zero.

Signed-off-by: Jianyong Wu <wujianyong@hygon.cn>
Fixes: #5321
(cherry picked from commit 73e78a5822)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-08-09 19:47:08 +02:00
096e42b366 Merge pull request #5335 from vvoland/5310-27.x
[27.x backport] gha: set permissions to read-only by default
2024-08-09 10:54:36 +02:00
984ef9072c plugins: don't panic on Close if PluginServer nil
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit 9c4480604e)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-09 10:48:40 +02:00
30c7951192 Merge pull request #5334 from vvoland/5289-27.x
[27.x backport] docs: refresh image versions in examples
2024-08-09 10:48:38 +02:00
54135b0724 gha: set permissions to read-only by default
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit e4d99b4b60)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-09 10:44:34 +02:00
40707e17b8 docs: refresh image versions in examples
use current LTS versions of ubuntu where suitable, remove uses of
ubuntu:23.10 (which reache EOL), and and update some other examples
to use more current versions.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b36522b473)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-09 10:42:40 +02:00
edd71d77c7 vendor: golang.org/x/sys v0.22.0
full diff: https://github.com/golang/sys/compare/v0.21.0...v0.22.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 501904d48f)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-08-08 13:57:57 +02:00
9593373f9e Merge pull request #5325 from vvoland/5324-27.x
[27.x backport] update to go1.21.13
2024-08-08 13:27:04 +02:00
5761e662f1 Merge pull request #5326 from vvoland/5303-27.x
[27.x backport] tests/run: fix flaky `RunAttachTermination` test
2024-08-07 12:31:40 +01:00
c7f3031f74 tests/run: fix flaky RunAttachTermination test
This test was just incorrect (and testing incorrect
behavior): it was checking that `docker run` exited with a `context
canceled` error after signalling the CLI/cancelling the command's
context, but this was incorrect (and was fixed in
991b1303da - which was when this test
started failing).

However, since this test assertion was happening inside of a goroutine,
it would sometimes pass if this assertion didn't get to run before the
test suite terminated. It was flaky because sometimes this assertion
inside the goroutine did get to execute, but after the test finished
execution, which is a big no-no.

As an aside, assertions inside goroutines are generally bad, and `govet`
even has a linter for this (but it only catches `t.Fatal` and `t.FailNow`
calls and not `assert.Xx`.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit eac83574c1)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-07 12:05:30 +02:00
53cb00a818 update to go1.21.13
- https://github.com/golang/go/issues?q=milestone%3AGo1.21.13+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.21.12...go1.21.13

go1.21.13 (released 2024-08-06) includes fixes to the go command, the
covdata command, and the bytes package. See the [Go 1.21.13 milestone](https://github.com/golang/go/issues?q=milestone%3AGo1.21.13+label%3ACherryPickApproved)
on our issue tracker for details.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 434d8b75e8)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-08-07 11:50:29 +02:00
a35c363ffc Merge pull request #5302 from laurazard/27-attach-exit-code
[27.1 backport] attach: wait for exit code from `ContainerWait`
2024-07-26 17:21:35 +02:00
1cf3637198 attach: wait for exit code from ContainerWait
Such as with `docker run`, if a user CTRL-Cs while attached to a
container, we should forward the signal and wait for the exit from
`ContainerWait`, instead of just returning.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit 7b46bfc5ac)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-07-26 15:48:37 +01:00
fd3157bf35 Merge pull request #5296 from vvoland/5295-27.0
[27.1 backport] attach: don't return context cancelled error
2024-07-25 09:59:38 +02:00
dfb8f2155a attach: don't return context cancelled error
In 3f0d90a2a9 we introduced a global
signal handler and made sure all the contexts passed into command
execution get (appropriately) cancelled when we get a SIGINT.

Due to that change, and how we use this context during `docker attach`,
we started to return the context cancelation error when a user signals
the running `docker attach`.

Since this is the intended behavior, we shouldn't return an error, so
this commit adds checks to ignore this specific error in this case.

Also adds a regression test.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit 66aa0f672c)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-07-25 09:51:11 +02:00
2e506cbb10 Merge pull request #5293 from laurazard/27-backport-flaky-tests
[27.0 backport] tests: fix flaxy TestCloseRunningCommand test
2024-07-24 13:38:49 +02:00
7f02bc9704 tests: fix other flaky connhelper tests
Follow up to cc68c66c95 (there were more
tests with incorrect syntax).

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit 4a7388f0dd)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-07-24 12:19:22 +01:00
8a6f7d849d tests: fix flaxy TestCloseRunningCommand test
Looks like this test was failing due to bad syntax on the `while` loop,
which caused it to die after 1 second. If the test took a bit longer,
the process would be dead before the following assertions run, causing
the test to fail/be flaky.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit cc68c66c95)
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-07-24 12:19:04 +01:00
1b2782ef64 Merge pull request #5267 from thaJeztah/27.1_bump_engine
[27.1] vendor: github.com/docker/docker v27.1.1
2024-07-24 10:50:40 +02:00
a74040315e vendor: github.com/docker/docker v27.1.1
no changes in vendored files

full diff: https://github.com/docker/docker/compare/v27.1.0...v27.1.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-24 00:23:56 +02:00
b889b2562c vendor: github.com/docker/docker v27.1.0
full diff:

- https://github.com/docker/docker/compare/v27.0.3....v27.1.0
- google.golang.org/genproto/googleapis/rpc 49dd2c1f3d...995d672761
- google.golang.org/genproto/googleapis/api: 49dd2c1f3d...83a465c022

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-23 13:37:57 +02:00
b24c7417e4 vendor: github.com/containerd/containerd v1.7.20
no changes in vendored code

full diff: https://github.com/containerd/containerd/compare/v1.7.19...v1.7.20

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 401048b9cb)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-23 13:37:29 +02:00
63125853e3 Merge pull request #5274 from thaJeztah/27.1_backport_compose_oom
Some checks failed
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (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
[27.1 backport] Add OomScoreAdj to "docker service create" and "docker stack"
2024-07-19 19:35:01 +02:00
c599566439 Allow for OomScoreAdj
Signed-off-by: plaurent <patrick@saint-laurent.us>
(cherry picked from commit aa2c2cd906)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 19:06:39 +02:00
fb19def3ce Merge pull request #5271 from thaJeztah/27.1_backport_custom_headers_env_var
[27.1 backport] add support for DOCKER_CUSTOM_HEADERS env-var (experimental)
2024-07-19 16:44:14 +02:00
bccd4786f7 Merge pull request #5270 from thaJeztah/27.1_backport_test_spring_cleaning
[27.1] test spring-cleaning
2024-07-19 15:09:56 +02:00
8992378c87 add support for DOCKER_CUSTOM_HEADERS env-var (experimental)
This environment variable allows for setting additional headers
to be sent by the client. Headers set through this environment
variable are added to headers set through the config-file (through
the HttpHeaders field).

This environment variable can be used in situations where headers
must be set for a specific invocation of the CLI, but should not
be set by default, and therefore cannot be set in the config-file.

WARNING: If both config and environment-variable are set, the environment
variable currently overrides all headers set in the configuration file.
This behavior may change in a future update, as we are considering the
environment variable to be appending to existing headers (and to only
override headers with the same name).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 6638deb9d6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 15:07:06 +02:00
f90273c340 Merge pull request #5269 from thaJeztah/27.1_backport_add_macos_apple_silicon
[27.1 backport] gha: update to macOS 13, add macOS 14 arm64 (Apple Silicon M1)
2024-07-19 13:43:25 +02:00
ca9636a1c3 test spring-cleaning
This makes a quick pass through our tests;

Discard output/err
----------------------------------------------

Many tests were testing for error-conditions, but didn't discard output.
This produced a lot of noise when running the tests, and made it hard
to discover if there were actual failures, or if the output was expected.
For example:

    === RUN   TestConfigCreateErrors
    Error: "create" requires exactly 2 arguments.
    See 'create --help'.

    Usage:  create [OPTIONS] CONFIG file|- [flags]

    Create a config from a file or STDIN
    Error: "create" requires exactly 2 arguments.
    See 'create --help'.

    Usage:  create [OPTIONS] CONFIG file|- [flags]

    Create a config from a file or STDIN
    Error: error creating config
    --- PASS: TestConfigCreateErrors (0.00s)

And after discarding output:

    === RUN   TestConfigCreateErrors
    --- PASS: TestConfigCreateErrors (0.00s)

Use sub-tests where possible
----------------------------------------------

Some tests were already set-up to use test-tables, and even had a usable
name (or in some cases "error" to check for). Change them to actual sub-
tests. Same test as above, but now with sub-tests and output discarded:

    === RUN   TestConfigCreateErrors
    === RUN   TestConfigCreateErrors/requires_exactly_2_arguments
    === RUN   TestConfigCreateErrors/requires_exactly_2_arguments#01
    === RUN   TestConfigCreateErrors/error_creating_config
    --- PASS: TestConfigCreateErrors (0.00s)
        --- PASS: TestConfigCreateErrors/requires_exactly_2_arguments (0.00s)
        --- PASS: TestConfigCreateErrors/requires_exactly_2_arguments#01 (0.00s)
        --- PASS: TestConfigCreateErrors/error_creating_config (0.00s)
    PASS

It's not perfect in all cases (in the above, there's duplicate "expected"
errors, but Go conveniently adds "#01" for the duplicate). There's probably
also various tests I missed that could still use the same changes applied;
we can improve these in follow-ups.

Set cmd.Args to prevent test-failures
----------------------------------------------

When running tests from my IDE, it compiles the tests before running,
then executes the compiled binary to run the tests. Cobra doesn't like
that, because in that situation `os.Args` is taken as argument for the
command that's executed. The command that's tested now sees the test-
flags as arguments (`-test.v -test.run ..`), which causes various tests
to fail ("Command XYZ does not accept arguments").

    # compile the tests:
    go test -c -o foo.test

    # execute the test:
    ./foo.test -test.v -test.run TestFoo
    === RUN   TestFoo
    Error: "foo" accepts no arguments.

The Cobra maintainers ran into the same situation, and for their own
use have added a special case to ignore `os.Args` in these cases;
https://github.com/spf13/cobra/blob/v1.8.1/command.go#L1078-L1083

    args := c.args

    // Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155
    if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" {
        args = os.Args[1:]
    }

Unfortunately, that exception is too specific (only checks for `cobra.test`),
so doesn't automatically fix the issue for other test-binaries. They did
provide a `cmd.SetArgs()` utility for this purpose
https://github.com/spf13/cobra/blob/v1.8.1/command.go#L276-L280

    // SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden
    // particularly useful when testing.
    func (c *Command) SetArgs(a []string) {
        c.args = a
    }

And the fix is to explicitly set the command's args to an empty slice to
prevent Cobra from falling back to using `os.Args[1:]` as arguments.

    cmd := newSomeThingCommand()
    cmd.SetArgs([]string{})

Some tests already take this issue into account, and I updated some tests
for this, but there's likely many other ones that can use the same treatment.

Perhaps the Cobra maintainers would accept a contribution to make their
condition less specific and to look for binaries ending with a `.test`
suffix (which is what compiled binaries usually are named as).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ab230240ad)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 13:37:27 +02:00
ad47d2a2c1 gha: update to macOS 13, add macOS 14 arm64 (Apple Silicon M1)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9617e8d0ce)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 13:28:07 +02:00
a2a0fb73ea Merge pull request #5263 from thaJeztah/27.1_backport_relax_pr_check
[27.1 backport] gha: check-pr-branch: verify major version only
2024-07-19 13:25:57 +02:00
16d6c90a94 Merge pull request #5265 from thaJeztah/27.1_backport_bump_buildx_compose
[27.0 backport] Dockerfile: update buildx to v0.16.1, compose to v2.29.0
2024-07-19 12:55:55 +02:00
f7be714467 gha: check-pr-branch: verify major version only
We'll be using release branches for minor version updates, so instead
of (e.g.) a 27.0 branch, we'll be using 27.x and continue using the
branch for minor version updates.

This patch changes the validation step to only compare against the
major version.

Co-authored-by: Cory Snider <corhere@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 6d8fcbb233)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 12:48:22 +02:00
33bf62c4cb Merge pull request #5261 from thaJeztah/27.1_backport_completion_enhancements
[27.0 backport] assorted fixes and enhancements for shell-completion
2024-07-19 12:05:33 +02:00
7e972d2f5c Merge pull request #5260 from thaJeztah/27.1_backport_fix-cli-login
[27.0 backport] fix: ctx cancellation on login prompt
2024-07-19 12:05:16 +02:00
a0791d0e8a Merge pull request #5266 from thaJeztah/27.1_backport_update_dependencies
[27.0 backport] update dependencies for v27.1
2024-07-19 10:46:06 +02:00
259d5e0f57 Dockerfile: update compose to v2.29.0
This is the version used in the dev-container, and for testing.

release notes: https://github.com/docker/compose/releases/tag/v2.29.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 77c0d83602)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:46:00 +02:00
c365c3cc8a Dockerfile: update buildx to v0.16.1
This is the version used in the dev-container, and for testing.

release notes:
https://github.com/docker/buildx/releases/tag/v0.16.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit d00e1abf55)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:46:00 +02:00
54c8eb7941 docs: fix typos and version for cli-docs-tool scripts
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 64a3fb82dc)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:04:44 +02:00
5ab44a4690 vendor: github.com/docker/cli-docs-tool v0.8.0
no changes in vendored code

full diff: https://github.com/docker/cli-docs-tool/compare/v0.7.0...v0.8.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit e3e9b99015)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:04:44 +02:00
33e5c87957 vendor: google.golang.org/genproto/googleapis/api 49dd2c1f3d0b
No changes in vendored files. This one got out of sync with the other modules
from the same repository.

full diff: d307bd883b...49dd2c1f3d

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit a77ba7eda8)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:04:24 +02:00
674f8d2979 vendor: github.com/prometheus/procfs v0.15.1
full diff: https://github.com/prometheus/procfs/compare/v0.12.0...v0.15.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit caa5d15e98)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:04:24 +02:00
2794ebd599 vendor: github.com/containerd/containerd v1.7.19
no changes in vendored code

full diff: https://github.com/containerd/containerd/compare/v1.7.18...v1.7.19

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 0f712827f1)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:04:24 +02:00
76863b46b7 vendor: golang.org/x/sync v0.7.0
no changes in vendored code

full diff: https://github.com/golang/sync/compare/v0.6.0...v0.7.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b28a1cd029)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:04:23 +02:00
e7bcae9053 vendor: golang.org/x/net v0.25.0
full diff: https://github.com/golang/net/compare/v0.24.0...v0.25.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit a0c4e56dea)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:03:51 +02:00
3577a17ce1 vendor: golang.org/x/crypto v0.23.0
no changes in vendored code

full diff: https://github.com/golang/crypto/compare/v0.22.0...v0.23.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 723130d7fe)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:03:51 +02:00
a8e2130643 vendor: golang.org/x/text v0.15.0
no changes in vendored files

full diff: https://github.com/golang/text/compare/v0.14.0...v0.15.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit d33ef57dcb)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:03:51 +02:00
4f1a67e06b vendor: golang.org/x/sys v0.21.0
full diff: https://github.com/golang/sys/compare/v0.19.0...v0.21.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 21dbedd419)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:03:51 +02:00
dfe3c0c074 vendor: github.com/klauspost/compress v1.17.9
full diff: https://github.com/klauspost/compress/compare/v1.17.4...v1.17.9

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit f8e7c0a0d6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 02:03:50 +02:00
66ec7142ae cli/command/container: add completion for --stop-signal
With this patch:

    docker run --stop-signal <TAB>
    ABRT  IOT      RTMAX-4   RTMIN     RTMIN+11  TSTP
    ALRM  KILL     RTMAX-5   RTMIN+1   RTMIN+12  TTIN
    BUS   PIPE     RTMAX-6   RTMIN+2   RTMIN+13  TTOU
    CHLD  POLL     RTMAX-7   RTMIN+3   RTMIN+14  URG
    CLD   PROF     RTMAX-8   RTMIN+4   RTMIN+15  USR1
    CONT  PWR      RTMAX-9   RTMIN+5   SEGV      USR2
    FPE   QUIT     RTMAX-10  RTMIN+6   STKFLT    VTALRM
    HUP   RTMAX    RTMAX-11  RTMIN+7   STOP      WINCH
    ILL   RTMAX-1  RTMAX-12  RTMIN+8   SYS       XCPU
    INT   RTMAX-2  RTMAX-13  RTMIN+9   TERM      XFSZ
    IO    RTMAX-3  RTMAX-14  RTMIN+10  TRAP

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b1c0ddca02)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:56:26 +02:00
ab8a2c0716 cli/command/container: add completion for --volumes-from
With this patch:

    docker run --volumes-from amazing_nobel
    amazing_cannon     boring_wozniak         determined_banzai
    elegant_solomon    reverent_booth         amazing_nobel

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit d6f78cdbb1)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:56:25 +02:00
d11e73d6e6 cli/command/container: add completion for --restart
With this patch:

    docker run --restart <TAB>
    always  no  on-failure  unless-stopped

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 7fe7223c2c)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:56:25 +02:00
36802176a7 cli/command/container: add completion for --cap-add, --cap-drop
With this patch:

    docker run --cap-add <TAB>
    ALL                     CAP_KILL                CAP_SETUID
    CAP_AUDIT_CONTROL       CAP_LEASE               CAP_SYSLOG
    CAP_AUDIT_READ          CAP_LINUX_IMMUTABLE     CAP_SYS_ADMIN
    CAP_AUDIT_WRITE         CAP_MAC_ADMIN           CAP_SYS_BOOT
    CAP_BLOCK_SUSPEND       CAP_MAC_OVERRIDE        CAP_SYS_CHROOT
    CAP_BPF                 CAP_MKNOD               CAP_SYS_MODULE
    CAP_CHECKPOINT_RESTORE  CAP_NET_ADMIN           CAP_SYS_NICE
    CAP_CHOWN               CAP_NET_BIND_SERVICE    CAP_SYS_PACCT
    CAP_DAC_OVERRIDE        CAP_NET_BROADCAST       CAP_SYS_PTRACE
    CAP_DAC_READ_SEARCH     CAP_NET_RAW             CAP_SYS_RAWIO
    CAP_FOWNER              CAP_PERFMON             CAP_SYS_RESOURCE
    CAP_FSETID              CAP_SETFCAP             CAP_SYS_TIME
    CAP_IPC_LOCK            CAP_SETGID              CAP_SYS_TTY_CONFIG
    CAP_IPC_OWNER           CAP_SETPCAP             CAP_WAKE_ALARM

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit f30158dbf8)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:56:25 +02:00
3926ed6b24 cli/context/store: Names(): fix panic when called with nil-interface
Before this, it would panic when a nil-interface was passed.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit e4dd8b1898)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:56:25 +02:00
787caf2fe4 cmd/docker: fix completion for --context
registerCompletionFuncForGlobalFlags was called from newDockerCommand,
at which time no context-store is initialized yet, so it would return
a nil value, probably resulting in `store.Names` to panic, but these
errors are not shown when running the completion. As a result, the flag
completion would fall back to completing from filenames.

This patch changes the function to dynamically get the context-store;
this fixes the problem mentioned above, because at the time the completion
function is _invoked_, the CLI is fully initialized, and does have a
context-store available.

A (non-exported) interface is defined to allow the function to accept
alternative implementations (not requiring a full command.DockerCLI).

Before this patch:

    docker context create one
    docker context create two

    docker --context <TAB>
    .DS_Store                   .idea/                      Makefile
    .dockerignore               .mailmap                    build/
    ...

With this patch:

    docker context create one
    docker context create two

    docker --context <TAB>
    default  one      two

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 42b68a3ed7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:56:24 +02:00
4473f48847 cli/command/container: provide flag-completion for "docker create"
"docker run" and "docker create" are mostly identical, so we can copy
the same completion functions,

We could possibly create a utility for this (similar to `addFlags()` which
configures both commands with the flags they share). I considered combining
his with `addFlags()`, but that utility is also used in various tests, in
which we don't need this feature, so keeping that for a future exercise.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 162d9748b9)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:56:24 +02:00
7a4062a4a9 cli/command/completion: add FromList utility
It's an alias for cobra.FixedCompletions but takes a variadic list
of strings, so that it's not needed to construct an array for this.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 5e7bcbeac6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:56:24 +02:00
d914a3f97e cli/command/completion: add EnvVarNames utility
EnvVarNames offers completion for environment-variable names. This
completion can be used for "--env" and "--build-arg" flags, which
allow obtaining the value of the given environment-variable if present
in the local environment, so we only should complete the names of the
environment variables, and not their value. This also prevents the
completion script from printing values of environment variables
containing sensitive values.

For example;

    export MY_VAR=hello
    docker run --rm --env MY_VAR alpine printenv MY_VAR
    hello

Before this patch:

    docker run --env GO
    GO111MODULE=auto        GOLANG_VERSION=1.21.12  GOPATH=/go              GOTOOLCHAIN=local

With this patch:

    docker run --env GO<tab>
    GO111MODULE     GOLANG_VERSION  GOPATH          GOTOOLCHAIN

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit e3427f341b)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:56:24 +02:00
68fe829c96 cli/command/completion: add FileNames utility
This is just a convenience function to allow defining completion to
use the default (complete with filenames and directories).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9207ff1046)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:56:24 +02:00
d60252954b cli/command/container: NewRunCommand: slight cleanup of completion
- explicitly suppress unhandled errors
- remove names for unused arguments

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit eed0e5b02a)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:56:23 +02:00
6cd1f6f26d Makefile: add completion target
Add a "completion" target to install the generated completion
scripts inside the dev-container. As generating this script
depends on the docker binary, it calls "make binary" first.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 3f3ecb94c5)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:55:36 +02:00
338e7a604c Dockerfile.dev: install bash-completion in dev container
It's not initialized, because there's no `docker` command installed
by default, but at least this makes sure that the basics are present
for testing.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 3d80b7b0a7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:55:36 +02:00
5dea9d881c Enable completion for 'image' sub commands
Signed-off-by: Dan Wallis <dan@wallis.nz>
(cherry picked from commit c7d46aa7a1)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:55:35 +02:00
4d67ef09c8 fix: ctx cancellation on login prompt
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
(cherry picked from commit c15ade0c64)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-19 01:46:16 +02:00
69a2c9fb6d Merge pull request #5250 from Benehiko/27.0-container-ctx
[27.0 backport] fix: container stream should not be terminated by ctx
2024-07-12 15:59:13 +02:00
333103d93f chore: restore ctx without cancel on container run
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2024-07-12 15:04:38 +02:00
d36bdb0d84 test: e2e SIGTERM attached container on docker run
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2024-07-12 15:04:38 +02:00
5777558c77 fix: container stream should not be terminated by ctx
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2024-07-12 15:04:26 +02:00
52848fb798 Merge pull request #5248 from vvoland/v27.0-5246
[27.0 backport] push: Don't default to `DOCKER_DEFAULT_PLATFORM`, improve message
2024-07-11 12:27:17 +02:00
bd43ca786b push: Improve note message and colors
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 6c04adc05e)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-07-11 12:06:02 +02:00
330a8e4e23 c8d: Remove docker convert mention
It's not merged yet.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit d40199440d)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-07-11 12:05:59 +02:00
e0b44d6d3f push: Don't default to DOCKER_DEFAULT_PLATFORM
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 4ce6e50e2e)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-07-11 12:05:57 +02:00
d41cb083c3 Merge pull request #5237 from dvdksn/backport_cli_reference_overview_base_cmd
[27.0 backport] cli reference overview base cmd
2024-07-05 19:55:31 +02:00
0a8bb6e5b4 chore: regenerate docs
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit dc22572e3e)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-07-05 15:20:48 +02:00
c777dd16df docs: update cli-docs-tool (v0.8.0)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 8549d250f6)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-07-05 15:20:45 +02:00
eea26c50dd docs: update links to docker cli reference
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 3d4c12af73)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-07-05 15:20:42 +02:00
1cf2c4efb3 docs: regenerate base command
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit bf33c8f10a)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-07-05 15:20:39 +02:00
c2b9c1474a docs: align heading structure for base command
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit b0650f281e)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-07-05 15:20:35 +02:00
598442d37d docs: remove frontmatter for base command
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit cfea2353b3)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-07-05 15:20:32 +02:00
c964b80e53 docs: rename cli.md to docker.md (base command)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 03961449aa)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-07-05 15:20:27 +02:00
0ca7be015e docs: remove empty docker base command reference
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit a683823383)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-07-05 15:20:22 +02:00
59fb099da2 Merge pull request #5227 from dvdksn/backport_buildx_canonical
[27.0 backport] docs: make buildx build the canonical reference doc
2024-07-04 11:20:46 +02:00
27bf78d335 docs: make buildx build the canonical reference doc
Move common flag descriptions to the buildx build reference, and make
that page the canonical page in docs. Also rewrite some content in
image_build to make clear that this page is only for the legacy builder.

Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit e91f0ded9c)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-07-04 09:19:10 +02:00
71b756be32 Merge pull request #5219 from vvoland/v27.0-5218
[27.0 backport] update to go1.21.12
2024-07-03 12:29:38 +02:00
63a27bc9b6 Merge pull request #5208 from thaJeztah/27.0_backport_bump_engine_27.0.3
[27.0 backport] vendor: github.com/docker/docker v27.0.3
2024-07-03 12:22:56 +02:00
5ed8f858cf update to go1.21.12
- https://github.com/golang/go/issues?q=milestone%3AGo1.21.12+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.21.11...go1.21.12

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

net/http: denial of service due to improper 100-continue handling

The net/http HTTP/1.1 client mishandled the case where a server responds to a request with an "Expect: 100-continue" header with a non-informational (200 or higher) status. This mishandling could leave a client connection in an invalid state, where the next request sent on the connection will fail.

An attacker sending a request to a net/http/httputil.ReverseProxy proxy can exploit this mishandling to cause a denial of service by sending "Expect: 100-continue" requests which elicit a non-informational response from the backend. Each such request leaves the proxy with an invalid connection, and causes one subsequent request using that connection to fail.

Thanks to Geoff Franks for reporting this issue.

This is CVE-2024-24791 and Go issue https://go.dev/issue/67555.
View the release notes for more information:
https://go.dev/doc/devel/release#go1.21.12

**- Description for the changelog**

```markdown changelog
Update Go runtime to 1.21.12
```

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit d73d7d4ed3)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-07-03 12:16:54 +02:00
6b99e0370c vendor: github.com/docker/docker v27.0.3
full diff: https://github.com/docker/docker/compare/v27.0.2...v27.0.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 2e6aaf05d4)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-01 12:30:22 +02:00
7d4bcd863a Merge pull request #5206 from thaJeztah/27.0_backport_docker_27.0.2
Some checks failed
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-12) (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
[27.0 backport] vendor: github.com/docker/docker v27.0.2
2024-06-28 15:56:30 +01:00
3134d55821 vendor: github.com/docker/docker v27.0.2
no diff, as it's the same commit tagged: https://github.com/docker/docker/compare/e953d76450b6...v27.0.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9455d61768)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-06-28 09:08:52 +02:00
912c1ddf8a Merge pull request #5202 from vvoland/vendor-docker
Some checks failed
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / e2e (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 23, experimental) (push) Has been cancelled
e2e / e2e (alpine, 23, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 26.1, experimental) (push) Has been cancelled
e2e / e2e (alpine, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, 27, experimental) (push) Has been cancelled
e2e / e2e (alpine, 27, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 23, experimental) (push) Has been cancelled
e2e / e2e (debian, 23, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 26.1, experimental) (push) Has been cancelled
e2e / e2e (debian, 26.1, non-experimental) (push) Has been cancelled
e2e / e2e (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / e2e (debian, 27, experimental) (push) Has been cancelled
e2e / e2e (debian, 27, non-experimental) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-12) (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
[27.0] vendor: github.com/docker/docker v27.0.2-dev (e953d76450b6)
2024-06-26 20:39:48 +02:00
c97e8091a6 vendor: github.com/docker/docker v27.0.2-dev (e953d76450b6)
full diff: 861fde8cc9...e953d76450

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-06-26 20:34:31 +02:00
82bd8158f7 Merge pull request #5201 from vvoland/vendor-docker
[27.0] vendor: github.com/docker/docker v27.0.2-dev (861fde8cc974)
2024-06-26 18:58:04 +01:00
8945848025 vendor: github.com/docker/docker v27.0.2-dev (861fde8cc974)
full diff: https://github.com/docker/docker/compare/v27.0.1...861fde8cc974

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-06-26 19:20:54 +02:00
b54897bcb8 Merge pull request #5199 from vvoland/v27.0-5191
[27.0 backport] gha/e2e: Update latest version to 27.0
2024-06-26 15:45:12 +02:00
cd560916f3 gha/e2e: Update latest version to 27.0
27.0 is out - update the latest version used for e2e and drop the 25.0

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 60775b6150)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-06-26 15:37:10 +02:00
9a101a955b Merge pull request #5198 from thaJeztah/27.0_backport_carry_fix_custom_ports 2024-06-26 14:19:52 +01:00
50fae20748 cli/config/credentials: ConvertToHostname: handle IP-addresses
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 8b0a7b025d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-06-26 15:05:38 +02:00
37533c2f55 Merge pull request #5197 from thaJeztah/27.0_backport_fix_custom_ports
[27.0 backport] re-introduced support for port numbers in docker registry URL
2024-06-26 15:04:51 +02:00
217971d481 re-introduced support for port numbers in docker registry URL
Signed-off-by: Carston Schilds <Carston.Schilds@visier.com>
(cherry picked from commit 2380481609)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-06-26 14:13:24 +02:00
fce24d5f8d Merge pull request #5192 from vvoland/v27.0-5189
[27.0 backport] update golangci-lint to v1.59.1
2024-06-26 13:43:56 +02:00
0e4f16f3bf Merge pull request #5190 from vvoland/vendor-docker
[27.0] vendor: github.com/docker/docker v27.0.1
2024-06-25 14:48:44 +02:00
6e35a78fd9 update golangci-lint to v1.59.1
full diff: https://github.com/golangci/golangci-lint/compare/v1.59.0...v1.59.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b5d1b4de1a)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-06-25 14:45:13 +02:00
bf1a701820 vendor: github.com/docker/docker v27.0.1
no change in vendored files, just changing a tag

full diff: https://github.com/docker/docker/compare/ff1e2c0de72a...v27.0.1

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-06-25 12:13:00 +02:00
2356 changed files with 124616 additions and 108719 deletions

View File

@ -1,6 +1,6 @@
/build/
/cmd/docker/winresources/versioninfo.json
/cmd/docker/winresources/*.syso
/cli/winresources/versioninfo.json
/cli/winresources/*.syso
/man/man*/
/man/vendor/
/man/go.sum

11
.gitattributes vendored
View File

@ -1,14 +1,3 @@
* text=auto
Dockerfile* linguist-language=Dockerfile
vendor.mod linguist-language=Go-Module
vendor.sum linguist-language=Go-Checksums
*.go -text diff=golang
# scripts directory contains shell scripts
# without extensions, so we need to force
scripts/** text=auto eol=lf
# shell scripts should always have LF
*.sh text eol=lf

View File

@ -19,14 +19,11 @@ Provide the following information:
**- How to verify it**
**- Human readable description for the release notes**
**- Description for the changelog**
<!--
Write a short (one line) summary that describes the changes in this
pull request for inclusion in the changelog.
It must be placed inside the below triple backticks section.
NOTE: Only fill this section if changes introduced in this PR are user-facing.
The PR must have a relevant impact/ label.
It must be placed inside the below triple backticks section:
-->
```markdown changelog

View File

@ -35,7 +35,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
-
name: Create matrix
id: platforms
@ -61,12 +61,17 @@ jobs:
- ""
- glibc
steps:
-
name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Build
uses: docker/bake-action@v6
uses: docker/bake-action@v5
with:
targets: ${{ matrix.target }}
set: |
@ -88,7 +93,7 @@ jobs:
fi
-
name: Upload artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v4
with:
name: ${{ env.ARTIFACT_NAME }}
path: /tmp/out/*
@ -99,12 +104,8 @@ jobs:
if: ${{ github.event_name != 'pull_request' && github.repository == 'docker/cli' }}
steps:
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_CLIBIN_USERNAME }}
password: ${{ secrets.DOCKERHUB_CLIBIN_TOKEN }}
name: Checkout
uses: actions/checkout@v4
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
@ -121,15 +122,21 @@ jobs:
type=semver,pattern={{version}}
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
type=sha
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_CLIBIN_USERNAME }}
password: ${{ secrets.DOCKERHUB_CLIBIN_TOKEN }}
-
name: Build and push image
uses: docker/bake-action@v6
uses: docker/bake-action@v5
with:
files: |
./docker-bake.hcl
cwd://${{ steps.meta.outputs.bake-file }}
${{ steps.meta.outputs.bake-file }}
targets: bin-image-cross
push: ${{ github.event_name != 'pull_request' }}
set: |
@ -143,7 +150,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
-
name: Create matrix
id: platforms
@ -163,12 +170,15 @@ jobs:
matrix:
platform: ${{ fromJson(needs.prepare-plugins.outputs.matrix) }}
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Build
uses: docker/bake-action@v6
uses: docker/bake-action@v5
with:
targets: plugins-cross
set: |

View File

@ -11,15 +11,15 @@ permissions:
on:
push:
branches:
- 'master'
- '[0-9]+.[0-9]+'
branches:
- 'master'
- '[0-9]+.[0-9]+'
- '[0-9]+.x'
tags:
- 'v*'
tags:
- 'v*'
pull_request:
# The branches below must be a subset of the branches above
branches: ["master"]
branches: [ "master" ]
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
@ -34,21 +34,26 @@ on:
jobs:
codeql:
runs-on: ubuntu-24.04
timeout-minutes: 10
runs-on: 'ubuntu-latest'
timeout-minutes: 360
env:
DISABLE_WARN_OUTSIDE_CONTAINER: '1'
permissions:
actions: read
contents: read
security-events: write
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 2
-
name: Checkout HEAD on PR
if: ${{ github.event_name == 'pull_request' }}
run: |
git checkout HEAD^2
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
# and is trying to be smart by searching for modules in every directory,
# including vendor directories. If no module is found, it's creating one
@ -61,19 +66,19 @@ jobs:
ln -s vendor.sum go.sum
-
name: Update Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: "1.25.5"
go-version: "1.22.12"
-
name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@v3
with:
languages: go
-
name: Autobuild
uses: github/codeql-action/autobuild@v4
uses: github/codeql-action/autobuild@v3
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@v3
with:
category: "/language:go"

View File

@ -25,26 +25,28 @@ on:
pull_request:
jobs:
tests:
e2e:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
target:
- local
- non-experimental
- experimental
- connhelper-ssh
base:
- alpine
- debian
engine-version:
- rc # latest rc
- 29 # latest
- 28 # latest - 1
- 25 # mirantis lts
- 27.0 # latest
- 26.1 # latest - 1
- 23.0 # mirantis lts
# TODO(krissetto) 19.03 needs a look, doesn't work ubuntu 22.04 (cgroup errors).
# we could have a separate job that tests it against ubuntu 20.04
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
-
name: Update daemon.json
run: |
@ -74,7 +76,7 @@ jobs:
TESTFLAGS: -coverprofile=/tmp/coverage/coverage.txt
-
name: Send to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v4
with:
files: ./build/coverage/coverage.txt
file: ./build/coverage/coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}

View File

@ -28,19 +28,22 @@ jobs:
ctn:
runs-on: ubuntu-24.04
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Test
uses: docker/bake-action@v6
uses: docker/bake-action@v5
with:
targets: test-coverage
-
name: Send to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v4
with:
files: ./build/coverage/coverage.txt
file: ./build/coverage/coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
host:
@ -53,32 +56,37 @@ jobs:
fail-fast: false
matrix:
os:
- 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)
- macos-13 # macOS 13 on Intel
- macos-14 # macOS 14 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: Prepare git
if: matrix.os == 'windows-latest'
run: |
git config --system core.autocrlf false
git config --system core.eol lf
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
path: ${{ env.GOPATH }}/src/github.com/docker/cli
-
name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: "1.25.5"
go-version: "1.22.12"
-
name: Test
run: |
go test -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/|/cmd/docker-trust')
go test -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/')
go tool cover -func=/tmp/coverage.txt
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
shell: bash
-
name: Send to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v4
with:
files: /tmp/coverage.txt
file: /tmp/coverage.txt
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
token: ${{ secrets.CODECOV_TOKEN }}

View File

@ -11,31 +11,24 @@ permissions:
on:
pull_request:
types: [opened, edited, labeled, unlabeled, synchronize]
types: [opened, edited, labeled, unlabeled]
jobs:
check-labels:
runs-on: ubuntu-24.04
timeout-minutes: 120 # guardrails timeout for the whole job
check-area-label:
runs-on: ubuntu-20.04
steps:
- name: Missing `area/` label
if: always() && contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'area/')
if: 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
check-changelog:
runs-on: ubuntu-24.04
timeout-minutes: 120 # guardrails timeout for the whole job
if: contains(join(github.event.pull_request.labels.*.name, ','), 'impact/')
runs-on: ubuntu-20.04
env:
HAS_IMPACT_LABEL: ${{ contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') }}
PR_BODY: |
${{ github.event.pull_request.body }}
steps:
@ -47,31 +40,22 @@ jobs:
# Strip empty lines
desc=$(echo "$block" | awk NF)
if [ "$HAS_IMPACT_LABEL" = "true" ]; then
if [ -z "$desc" ]; then
echo "::error::Changelog section is empty. Please provide a description for the changelog."
exit 1
fi
if [ -z "$desc" ]; then
echo "::error::Changelog section is empty. Provide a description for the changelog."
exit 1
fi
len=$(echo -n "$desc" | wc -c)
if [[ $len -le 6 ]]; then
echo "::error::Description looks too short: $desc"
exit 1
fi
else
if [ -n "$desc" ]; then
echo "::error::PR has a changelog description, but no changelog label"
echo "::error::Please add the relevant 'impact/' label to the PR or remove the changelog description"
exit 1
fi
len=$(echo -n "$desc" | wc -c)
if [[ $len -le 6 ]]; then
echo "::error::Description looks too short: $desc"
exit 1
fi
echo "This PR will be included in the release notes with the following note:"
echo "$desc"
check-pr-branch:
runs-on: ubuntu-24.04
timeout-minutes: 120 # guardrails timeout for the whole job
runs-on: ubuntu-20.04
env:
PR_TITLE: ${{ github.event.pull_request.title }}
steps:

View File

@ -36,9 +36,12 @@ jobs:
- validate-vendor
- update-authors # ensure authors update target runs fine
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Run
uses: docker/bake-action@v6
uses: docker/bake-action@v5
with:
targets: ${{ matrix.target }}
@ -48,7 +51,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
-
name: Generate
shell: 'script --return --quiet --command "bash {0}"'
@ -74,7 +77,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
-
name: Run
shell: 'script --return --quiet --command "bash {0}"'

4
.gitignore vendored
View File

@ -8,8 +8,8 @@
Thumbs.db
.editorconfig
/build/
/cmd/docker/winresources/versioninfo.json
/cmd/docker/winresources/*.syso
/cli/winresources/versioninfo.json
/cli/winresources/*.syso
profile.out
# top-level go.mod is not meant to be checked in

View File

@ -1,236 +1,176 @@
version: "2"
run:
# prevent golangci-lint from deducting the go version to lint for through go.mod,
# 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.25.5"
timeout: 5m
issues:
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-issues-per-linter: 0
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0
formatters:
enable:
- gofumpt # Detects whether code was gofumpt-ed.
- goimports
exclusions:
generated: strict
linters:
enable:
- asasalint # Detects "[]any" used as argument for variadic "func(...any)".
- bodyclose
- copyloopvar # Detects places where loop variables are copied.
- copyloopvar # Detects places where loop variables are copied.
- depguard
- dogsled # Detects assignments with too many blank identifiers.
- dupword # Detects duplicate words.
- durationcheck # Detect cases where two time.Duration values are being multiplied in possibly erroneous ways.
- errcheck
- errchkjson # Detects unsupported types passed to json encoding functions and reports if checks for the returned error can be omitted.
- exhaustive # Detects missing options in enum switch statements.
- exptostd # Detects functions from golang.org/x/exp/ that can be replaced by std functions.
- fatcontext # Detects nested contexts in loops and function literals.
- forbidigo
- gocheckcompilerdirectives # Detects invalid go compiler directive comments (//go:).
- gocritic # Metalinter; detects bugs, performance, and styling issues.
- dogsled
- dupword # Detects duplicate words.
- durationcheck
- errchkjson
- gocritic # Metalinter; detects bugs, performance, and styling issues.
- gocyclo
- gosec # Detects security problems.
- gofumpt # Detects whether code was gofumpt-ed.
- goimports
- gosec # Detects security problems.
- gosimple
- govet
- iface # Detects incorrect use of interfaces. Currently only used for "identical" interfaces in the same package.
- importas # Enforces consistent import aliases.
- ineffassign
- makezero # Finds slice declarations with non-zero initial length.
- mirror # Detects wrong mirror patterns of bytes/strings usage.
- misspell # Detects commonly misspelled English words in comments.
- nakedret # Detects uses of naked returns.
- nilnesserr # Detects returning nil errors. It combines the features of nilness and nilerr,
- nosprintfhostport # Detects misuse of Sprintf to construct a host with port in a URL.
- nolintlint # Detects ill-formed or insufficient nolint directives.
- perfsprint # Detects fmt.Sprintf uses that can be replaced with a faster alternative.
- prealloc # Detects slice declarations that could potentially be pre-allocated.
- predeclared # Detects code that shadows one of Go's predeclared identifiers
- reassign # Detects reassigning a top-level variable in another package.
- revive # Metalinter; drop-in replacement for golint.
- spancheck # Detects mistakes with OpenTelemetry/Census spans.
- lll
- megacheck
- misspell # Detects commonly misspelled English words in comments.
- nakedret
- nilerr # Detects code that returns nil even if it checks that the error is not nil.
- nolintlint # Detects ill-formed or insufficient nolint directives.
- perfsprint # Detects fmt.Sprintf uses that can be replaced with a faster alternative.
- prealloc # Detects slice declarations that could potentially be pre-allocated.
- predeclared # Detects code that shadows one of Go's predeclared identifiers
- reassign
- revive # Metalinter; drop-in replacement for golint.
- staticcheck
- thelper # Detects test helpers without t.Helper().
- tparallel # Detects inappropriate usage of t.Parallel().
- unconvert # Detects unnecessary type conversions.
- stylecheck # Replacement for golint
- tenv # Detects using os.Setenv instead of t.Setenv.
- thelper # Detects test helpers without t.Helper().
- tparallel # Detects inappropriate usage of t.Parallel().
- typecheck
- unconvert # Detects unnecessary type conversions.
- unparam
- unused
- usestdlibvars # Detects the possibility to use variables/constants from the Go standard library.
- usetesting # Reports uses of functions with replacement inside the testing package.
- wastedassign # Detects wasted assignment statements.
- usestdlibvars
- vet
- wastedassign
disable:
- errcheck
settings:
depguard:
rules:
main:
deny:
- pkg: "github.com/containerd/containerd/errdefs"
desc: The containerd errdefs package was migrated to a separate module. Use github.com/containerd/errdefs instead.
- pkg: "github.com/containerd/containerd/log"
desc: The containerd log package was migrated to a separate module. Use github.com/containerd/log instead.
- pkg: "github.com/containerd/containerd/pkg/userns"
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"
desc: Use github.com/google/uuid instead.
- pkg: "io/ioutil"
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
run:
timeout: 5m
forbidigo:
forbid:
- pkg: ^regexp$
pattern: ^regexp\.MustCompile
msg: Use internal/lazyregexp.New instead.
linters-settings:
depguard:
rules:
main:
deny:
- pkg: io/ioutil
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
gocyclo:
min-complexity: 16
gosec:
excludes:
- G104 # G104: Errors unhandled; (TODO: reduce unhandled errors, or explicitly ignore)
- G113 # G113: Potential uncontrolled memory consumption in Rat.SetString (CVE-2022-23772); (only affects go < 1.16.14. and go < 1.17.7)
- G115 # G115: integer overflow conversion; (TODO: verify these: https://github.com/docker/cli/issues/5584)
- G306 # G306: Expect WriteFile permissions to be 0600 or less (too restrictive; also flags "0o644" permissions)
- G307 # G307: Deferring unsafe method "*os.File" on type "Close" (also EXC0008); (TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close")
govet:
enable:
- shadow
settings:
shadow:
strict: true
lll:
line-length: 200
nakedret:
command: nakedret
pattern: ^(?P<path>.*?\\.go):(?P<line>\\d+)\\s*(?P<message>.*)$
gocyclo:
min-complexity: 16
revive:
rules:
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#import-shadowing
- name: import-shadowing
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-block
- name: empty-block
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines
- name: empty-lines
severity: warning
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#use-any
- name: use-any
severity: warning
disabled: false
gosec:
excludes:
- G104 # G104: Errors unhandled; (TODO: reduce unhandled errors, or explicitly ignore)
- G115 # G115: integer overflow conversion; (TODO: verify these: https://github.com/docker/cli/issues/5584)
- G306 # G306: Expect WriteFile permissions to be 0600 or less (too restrictive; also flags "0o644" permissions)
- G307 # G307: Deferring unsafe method "*os.File" on type "Close" (also EXC0008); (TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close")
issues:
# The default exclusion rules are a bit too permissive, so copying the relevant ones below
exclude-use-default: false
govet:
enable:
- shadow
settings:
shadow:
strict: true
# This option has been defined when Go modules was not existed and when the
# golangci-lint core was different, this is not something we still recommend.
exclude-dirs-use-default: false
lll:
line-length: 200
exclude:
- parameter .* always receives
importas:
# Do not allow unaliased imports of aliased packages.
no-unaliased: true
exclude-files:
- cli/compose/schema/bindata.go
- .*generated.*
alias:
# 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"
- pkg: gotest.tools/v3/assert/cmp
alias: is
nakedret:
# Disallow naked returns if func has more lines of code than this setting.
# Default: 30
max-func-lines: 0
staticcheck:
checks:
- all
- -QF1008 # Omit embedded fields from selector expression; https://staticcheck.dev/docs/checks/#QF1008
revive:
rules:
- name: empty-block # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-block
- name: empty-lines # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines
- name: import-shadowing # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#import-shadowing
- name: line-length-limit # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#line-length-limit
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
context-background: false # FIXME(thaJeztah): Disable `context.Background()` detections; should be automatically disabled on Go < 1.24; see https://github.com/docker/cli/pull/5835#issuecomment-2665302478
context-todo: false # FIXME(thaJeztah): Disable `context.TODO()` detections; should be automatically disabled on Go < 1.24; see https://github.com/docker/cli/pull/5835#issuecomment-2665302478
exclusions:
# We prefer to use an "linters.exclusions.rules" so that new "default" exclusions are not
exclude-rules:
# We prefer to use an "exclude-list" so that new "default" exclusions are not
# automatically inherited. We can decide whether or not to follow upstream
# defaults when updating golang-ci-lint versions.
# Unfortunately, this means we have to copy the whole exclusion pattern, as
# (unlike the "include" option), the "exclude" option does not take exclusion
# ID's.
#
# These exclusion patterns are copied from the default excludes at:
# https://github.com/golangci/golangci-lint/blob/v1.61.0/pkg/config/issues.go#L11-L104
# These exclusion patterns are copied from the default excluses at:
# https://github.com/golangci/golangci-lint/blob/v1.44.0/pkg/config/issues.go#L10-L104
#
# The default list of exclusions can be found at:
# https://golangci-lint.run/usage/false-positives/#default-exclusions
generated: strict
rules:
# EXC0003
- text: "func name will be used as test\\.Test.* by other packages, and that stutters; consider calling this"
linters:
- revive
# EXC0001
- text: "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*print(f|ln)?|os\\.(Un)?Setenv). is not checked"
linters:
- errcheck
# EXC0003
- text: "func name will be used as test\\.Test.* by other packages, and that stutters; consider calling this"
linters:
- revive
# EXC0006
- text: "Use of unsafe calls should be audited"
linters:
- gosec
# EXC0007
- text: "Subprocess launch(ed with variable|ing should be audited)"
linters:
- gosec
# EXC0009
- text: "(Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)"
linters:
- gosec
# EXC0010
- text: "Potential file inclusion via variable"
linters:
- gosec
# EXC0007
- text: "Subprocess launch(ed with variable|ing should be audited)"
linters:
- gosec
# TODO: make sure all packages have a description. Currently, there's 67 packages without.
- text: "package-comments: should have a package comment"
linters:
- revive
# FIXME temporarily suppress these (see https://github.com/gotestyourself/gotest.tools/issues/272)
- text: "SA1019: (assert|cmp|is)\\.ErrorType is deprecated"
linters:
- staticcheck
# Exclude some linters from running on tests files.
- path: _test\.go
linters:
- errcheck
- gosec
- text: "ST1000: at least one file in a package should have a package comment"
linters:
- stylecheck
# EXC0009
- text: "(Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)"
linters:
- gosec
# Allow "err" and "ok" vars to shadow existing declarations, otherwise we get too many false positives.
- text: '^shadow: declaration of "(err|ok)" shadows declaration'
linters:
- govet
# EXC0010
- text: "Potential file inclusion via variable"
linters:
- gosec
# TODO: make sure all packages have a description. Currently, there's 67 packages without.
- text: "package-comments: should have a package comment"
linters:
- revive
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-issues-per-linter: 0
# Exclude some linters from running on tests files.
- path: _test\.go
linters:
- errcheck
- gosec
- text: "ST1000: at least one file in a package should have a package comment"
linters:
- staticcheck
# Allow "err" and "ok" vars to shadow existing declarations, otherwise we get too many false positives.
- text: '^shadow: declaration of "(err|ok)" shadows declaration'
linters:
- govet
# Ignore for cli/command/formatter/tabwriter, which is forked from go stdlib, so we want to align with it.
- text: '^(ST1020|ST1022): comment on exported'
path: "cli/command/formatter/tabwriter"
linters:
- staticcheck
# 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
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0

View File

@ -43,7 +43,6 @@ Alexis Couvreur <alexiscouvreur.pro@gmail.com>
Alicia Lauerman <alicia@eta.im> <allydevour@me.com>
Allen Sun <allensun.shl@alibaba-inc.com> <allen.sun@daocloud.io>
Allen Sun <allensun.shl@alibaba-inc.com> <shlallen1990@gmail.com>
Allie Sadler <allie.sadler@docker.com>
Andrew Weiss <andrew.weiss@docker.com> <andrew.weiss@microsoft.com>
Andrew Weiss <andrew.weiss@docker.com> <andrew.weiss@outlook.com>
André Martins <aanm90@gmail.com> <martins@noironetworks.com>
@ -64,14 +63,8 @@ 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@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>
@ -153,8 +146,6 @@ 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>
@ -204,8 +195,6 @@ Gaetan de Villele <gdevillele@gmail.com>
Gang Qiao <qiaohai8866@gmail.com> <1373319223@qq.com>
George Kontridze <george@bugsnag.com>
Gerwim Feiken <g.feiken@tfe.nl> <gerwim@gmail.com>
Giau. Tran Minh <hello@giautm.dev>
Giau. Tran Minh <hello@giautm.dev> <12751435+giautm@users.noreply.github.com>
Giampaolo Mancini <giampaolo@trampolineup.com>
Gopikannan Venugopalsamy <gopikannan.venugopalsamy@gmail.com>
Gou Rao <gou@portworx.com> <gourao@users.noreply.github.com>
@ -225,7 +214,6 @@ Günther Jungbluth <gunther@gameslabs.net>
Hakan Özler <hakan.ozler@kodcu.com>
Hao Shu Wei <haosw@cn.ibm.com>
Hao Shu Wei <haosw@cn.ibm.com> <haoshuwei1989@163.com>
Harald Albers <github@albersweb.de>
Harald Albers <github@albersweb.de> <albers@users.noreply.github.com>
Harold Cooper <hrldcpr@gmail.com>
Harry Zhang <harryz@hyper.sh> <harryzhang@zju.edu.cn>
@ -342,8 +330,6 @@ Lajos Papp <lajos.papp@sequenceiq.com> <lalyos@yahoo.com>
Lei Jitang <leijitang@huawei.com>
Lei Jitang <leijitang@huawei.com> <leijitang@gmail.com>
Li Fu Bang <lifubang@acmcoder.com>
Li Yi <denverdino@gmail.com>
Li Yi <denverdino@gmail.com> <weiyuan.yl@alibaba-inc.com>
Liang Mingqiang <mqliang.zju@gmail.com>
Liang-Chi Hsieh <viirya@gmail.com>
Liao Qingwei <liaoqingwei@huawei.com>
@ -353,7 +339,6 @@ Lokesh Mandvekar <lsm5@fedoraproject.org> <lsm5@redhat.com>
Lorenzo Fontana <lo@linux.com> <fontanalorenzo@me.com>
Louis Opter <kalessin@kalessin.fr>
Louis Opter <kalessin@kalessin.fr> <louis@dotcloud.com>
Lovekesh Kumar <lovekesh.kumar@rtcamp.com>
Luca Favatella <luca.favatella@erlang-solutions.com> <lucafavatella@users.noreply.github.com>
Luke Marsden <me@lukemarsden.net> <luke@digital-crocus.com>
Lyn <energylyn@zju.edu.cn>
@ -411,7 +396,6 @@ Mike Dalton <mikedalton@github.com> <19153140+mikedalton@users.noreply.github.co
Mike Goelzer <mike.goelzer@docker.com> <mgoelzer@docker.com>
Milind Chawre <milindchawre@gmail.com>
Misty Stanley-Jones <misty@docker.com> <misty@apache.org>
Mohammad Hossein <mhm98035@gmail.com>
Mohit Soni <mosoni@ebay.com> <mohitsoni1989@gmail.com>
Moorthy RS <rsmoorthy@gmail.com> <rsmoorthy@users.noreply.github.com>
Morten Hekkvang <morten.hekkvang@sbab.se>
@ -435,7 +419,6 @@ O.S. Tezer <ostezer@gmail.com> <ostezer@users.noreply.github.com>
Oh Jinkyun <tintypemolly@gmail.com> <tintypemolly@Ohui-MacBook-Pro.local>
Oliver Pomeroy <oppomeroy@gmail.com>
Ouyang Liduo <oyld0210@163.com>
Patrick St. laurent <patrick@saint-laurent.us>
Patrick Stapleton <github@gdi2290.com>
Paul Liljenberg <liljenberg.paul@gmail.com> <letters@paulnotcom.se>
Pavel Tikhomirov <ptikhomirov@virtuozzo.com> <ptikhomirov@parallels.com>
@ -515,7 +498,6 @@ Stephen Day <stevvooe@gmail.com> <stephen.day@docker.com>
Stephen Day <stevvooe@gmail.com> <stevvooe@users.noreply.github.com>
Steve Desmond <steve@vtsv.ca> <stevedesmond-ca@users.noreply.github.com>
Steve Richards <steve.richards@docker.com> stevejr <>
Stuart Williams <pid@pidster.com>
Sun Gengze <690388648@qq.com>
Sun Jianbo <wonderflow.sun@gmail.com>
Sun Jianbo <wonderflow.sun@gmail.com> <wonderflow@zju.edu.cn>

41
AUTHORS
View File

@ -48,7 +48,6 @@ Alfred Landrum <alfred.landrum@docker.com>
Ali Rostami <rostami.ali@gmail.com>
Alicia Lauerman <alicia@eta.im>
Allen Sun <allensun.shl@alibaba-inc.com>
Allie Sadler <allie.sadler@docker.com>
Alvin Deng <alvin.q.deng@utexas.edu>
Amen Belayneh <amenbelayneh@gmail.com>
Amey Shrivastava <72866602+AmeyShrivastava@users.noreply.github.com>
@ -63,7 +62,6 @@ 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>
@ -83,16 +81,13 @@ Antonis Kalipetis <akalipetis@gmail.com>
Anusha Ragunathan <anusha.ragunathan@docker.com>
Ao Li <la9249@163.com>
Arash Deshmeh <adeshmeh@ca.ibm.com>
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@docker.com>
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
Bardia Keyoumarsi <bkeyouma@ucsc.edu>
Barnaby Gray <barnaby@pickle.me.uk>
@ -137,12 +132,9 @@ 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>
@ -197,7 +189,6 @@ Daisuke Ito <itodaisuke00@gmail.com>
dalanlan <dalanlan925@gmail.com>
Damien Nadé <github@livna.org>
Dan Cotora <dan@bluevision.ro>
Dan Wallis <dan@wallis.nz>
Danial Gharib <danial.mail.gh@gmail.com>
Daniel Artine <daniel.artine@ufrj.br>
Daniel Cassidy <mail@danielcassidy.me.uk>
@ -224,7 +215,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 <david.dooling@docker.com>
David Dooling <dooling@gmail.com>
David Gageot <david@gageot.net>
David Karlsson <david.karlsson@docker.com>
David le Blanc <systemmonkey42@users.noreply.github.com>
@ -246,7 +237,6 @@ Deshi Xiao <dxiao@redhat.com>
Dharmit Shah <shahdharmit@gmail.com>
Dhawal Yogesh Bhanushali <dbhanushali@vmware.com>
Dieter Reuter <dieter.reuter@me.com>
Dilep Dev <34891655+DilepDev@users.noreply.github.com>
Dima Stopel <dima@twistlock.com>
Dimitry Andric <d.andric@activevideo.com>
Ding Fei <dingfei@stars.org.cn>
@ -269,7 +259,6 @@ 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>
@ -319,8 +308,6 @@ George MacRorie <gmacr31@gmail.com>
George Margaritis <gmargaritis@protonmail.com>
George Xie <georgexsh@gmail.com>
Gianluca Borello <g.borello@gmail.com>
Giau. Tran Minh <hello@giautm.dev>
Giedrius Jonikas <giedriusj1@gmail.com>
Gildas Cuisinier <gildas.cuisinier@gcuisinier.net>
Gio d'Amelio <giodamelio@gmail.com>
Gleb Stsenov <gleb.stsenov@gmail.com>
@ -350,7 +337,6 @@ 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>
@ -358,7 +344,6 @@ Hugo Gabriel Eyherabide <hugogabriel.eyherabide@gmail.com>
huqun <huqun@zju.edu.cn>
Huu Nguyen <huu@prismskylabs.com>
Hyzhou Zhy <hyzhou.zhy@alibaba-inc.com>
Iain MacDonald <IJMacD@gmail.com>
Iain Samuel McLean Elder <iain@isme.es>
Ian Campbell <ian.campbell@docker.com>
Ian Philpot <ian.philpot@microsoft.com>
@ -408,7 +393,6 @@ Jesse Adametz <jesseadametz@gmail.com>
Jessica Frazelle <jess@oxide.computer>
Jezeniel Zapanta <jpzapanta22@gmail.com>
Jian Zhang <zhangjian.fnst@cn.fujitsu.com>
Jianyong Wu <wujianyong@hygon.cn>
Jie Luo <luo612@zju.edu.cn>
Jilles Oldenbeuving <ojilles@gmail.com>
Jim Chen <njucjc@gmail.com>
@ -462,7 +446,6 @@ Julian <gitea+julian@ic.thejulian.uk>
Julien Barbier <write0@gmail.com>
Julien Kassar <github@kassisol.com>
Julien Maitrehenry <julien.maitrehenry@me.com>
Julio Cesar Garcia <juliogarciamelgarejo@gmail.com>
Justas Brazauskas <brazauskasjustas@gmail.com>
Justin Chadwell <me@jedevc.com>
Justin Cormack <justin.cormack@docker.com>
@ -507,22 +490,19 @@ Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp>
Kyle Mitofsky <Kylemit@gmail.com>
Lachlan Cooper <lachlancooper@gmail.com>
Lai Jiangshan <jiangshanlai@gmail.com>
Lajos Papp <lajos.papp@sequenceiq.com>
Lars Kellogg-Stedman <lars@redhat.com>
Laura Brehm <laurabrehm@hey.com>
Laura Frank <ljfrank@gmail.com>
Laurent Erignoux <lerignoux@gmail.com>
Laurent Goderre <laurent.goderre@docker.com>
Lee Gaines <eightlimbed@gmail.com>
Lei Jitang <leijitang@huawei.com>
Lennie <github@consolejunkie.net>
lentil32 <lentil32@icloud.com>
Leo Gallucci <elgalu3@gmail.com>
Leonid Skorospelov <leosko94@gmail.com>
Lewis Daly <lewisdaly@me.com>
Li Fu Bang <lifubang@acmcoder.com>
Li Yi <denverdino@gmail.com>
Li Zeghong <zeghong@hotmail.com>
Li Yi <weiyuan.yl@alibaba-inc.com>
Liang-Chi Hsieh <viirya@gmail.com>
Lihua Tang <lhtang@alauda.io>
Lily Guo <lily.guo@docker.com>
@ -535,7 +515,6 @@ lixiaobing10051267 <li.xiaobing1@zte.com.cn>
Lloyd Dewolf <foolswisdom@gmail.com>
Lorenzo Fontana <lo@linux.com>
Louis Opter <kalessin@kalessin.fr>
Lovekesh Kumar <lovekesh.kumar@rtcamp.com>
Luca Favatella <luca.favatella@erlang-solutions.com>
Luca Marturana <lucamarturana@gmail.com>
Lucas Chan <lucas-github@lucaschan.com>
@ -580,7 +559,6 @@ Matt Robenolt <matt@ydekproductions.com>
Matteo Orefice <matteo.orefice@bites4bits.software>
Matthew Heon <mheon@redhat.com>
Matthieu Hauglustaine <matt.hauglustaine@gmail.com>
Matthieu MOREL <matthieu.morel35@gmail.com>
Mauro Porras P <mauroporrasp@gmail.com>
Max Shytikov <mshytikov@gmail.com>
Max-Julian Pogner <max-julian@pogner.at>
@ -588,7 +566,6 @@ Maxime Petazzoni <max@signalfuse.com>
Maximillian Fan Xavier <maximillianfx@gmail.com>
Mei ChunTao <mei.chuntao@zte.com.cn>
Melroy van den Berg <melroy@melroy.org>
Mert Şişmanoğlu <mert190737fb@gmail.com>
Metal <2466052+tedhexaflow@users.noreply.github.com>
Micah Zoltu <micah@newrelic.com>
Michael A. Smith <michael@smith-li.com>
@ -601,7 +578,6 @@ 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>
@ -622,9 +598,7 @@ Mindaugas Rukas <momomg@gmail.com>
Miroslav Gula <miroslav.gula@naytrolabs.com>
Misty Stanley-Jones <misty@docker.com>
Mohammad Banikazemi <mb@us.ibm.com>
Mohammad Hossein <mhm98035@gmail.com>
Mohammed Aaqib Ansari <maaquib@gmail.com>
Mohammed Aminu Futa <mohammedfuta2000@gmail.com>
Mohini Anne Dsouza <mohini3917@gmail.com>
Moorthy RS <rsmoorthy@gmail.com>
Morgan Bauer <mbauer@us.ibm.com>
@ -659,11 +633,9 @@ Nicolas De Loof <nicolas.deloof@gmail.com>
Nikhil Chawla <chawlanikhil24@gmail.com>
Nikolas Garofil <nikolas.garofil@uantwerpen.be>
Nikolay Milovanov <nmil@itransformers.net>
NinaLua <iturf@sina.cn>
Nir Soffer <nsoffer@redhat.com>
Nishant Totla <nishanttotla@gmail.com>
NIWA Hideyuki <niwa.niwa@nifty.ne.jp>
Noah Silas <noah@hustle.com>
Noah Treuhaft <noah.treuhaft@docker.com>
O.S. Tezer <ostezer@gmail.com>
Oded Arbel <oded@geek.co.il>
@ -681,12 +653,10 @@ Patrick Böänziger <patrick.baenziger@bsi-software.com>
Patrick Daigle <114765035+pdaig@users.noreply.github.com>
Patrick Hemmer <patrick.hemmer@gmail.com>
Patrick Lang <plang@microsoft.com>
Patrick St. laurent <patrick@saint-laurent.us>
Paul <paul9869@gmail.com>
Paul Kehrer <paul.l.kehrer@gmail.com>
Paul Lietar <paul@lietar.net>
Paul Mulders <justinkb@gmail.com>
Paul Rogalski <mail@paul-rogalski.de>
Paul Seyfert <pseyfert.mathphys@gmail.com>
Paul Weaver <pauweave@cisco.com>
Pavel Pospisil <pospispa@gmail.com>
@ -708,6 +678,7 @@ Philip Alexander Etling <paetling@gmail.com>
Philipp Gillé <philipp.gille@gmail.com>
Philipp Schmied <pschmied@schutzwerk.com>
Phong Tran <tran.pho@northeastern.edu>
pidster <pid@pidster.com>
Pieter E Smit <diepes@github.com>
pixelistik <pixelistik@users.noreply.github.com>
Pratik Karki <prertik@outlook.com>
@ -767,7 +738,6 @@ Samuel Cochran <sj26@sj26.com>
Samuel Karp <skarp@amazon.com>
Sandro Jäckel <sandro.jaeckel@gmail.com>
Santhosh Manohar <santhosh@docker.com>
Sarah Sanders <sarah.sanders@docker.com>
Sargun Dhillon <sargun@netflix.com>
Saswat Bhattacharya <sas.saswat@gmail.com>
Saurabh Kumar <saurabhkumar0184@gmail.com>
@ -800,7 +770,6 @@ Spencer Brown <spencer@spencerbrown.org>
Spring Lee <xi.shuai@outlook.com>
squeegels <lmscrewy@gmail.com>
Srini Brahmaroutu <srbrahma@us.ibm.com>
Stavros Panakakis <stavrospanakakis@gmail.com>
Stefan S. <tronicum@user.github.com>
Stefan Scherer <stefan.scherer@docker.com>
Stefan Weil <sw@weilnetz.de>
@ -811,7 +780,6 @@ Steve Durrheimer <s.durrheimer@gmail.com>
Steve Richards <steve.richards@docker.com>
Steven Burgess <steven.a.burgess@hotmail.com>
Stoica-Marcu Floris-Andrei <floris.sm@gmail.com>
Stuart Williams <pid@pidster.com>
Subhajit Ghosh <isubuz.g@gmail.com>
Sun Jianbo <wonderflow.sun@gmail.com>
Sune Keller <absukl@almbrand.dk>
@ -899,11 +867,9 @@ Wang Yumu <37442693@qq.com>
Wataru Ishida <ishida.wataru@lab.ntt.co.jp>
Wayne Song <wsong@docker.com>
Wen Cheng Ma <wenchma@cn.ibm.com>
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>
@ -942,4 +908,3 @@ Zhuo Zhi <h.dwwwwww@gmail.com>
Átila Camurça Alves <camurca.home@gmail.com>
Александр Менщиков <__Singleton__@hackerdom.ru>
徐俊杰 <paco.xu@daocloud.io>
林博仁 Buo-ren Lin <Buo.Ren.Lin@gmail.com>

View File

@ -66,7 +66,7 @@ anybody starts working on it.
We are always thrilled to receive pull requests. We do our best to process them
quickly. If your pull request is not accepted on the first try,
don't get discouraged! Our contributor's guide explains [the review process we
use for simple changes](https://github.com/docker/docker/blob/master/project/REVIEWING.md).
use for simple changes](https://docs.docker.com/opensource/workflow/make-a-contribution/).
### Talking to other Docker users and contributors
@ -124,8 +124,8 @@ submitting a pull request.
Update the documentation when creating or modifying features. Test your
documentation changes for clarity, concision, and correctness, as well as a
clean documentation build. See our contributors guide for [our style
guide](https://docs.docker.com/contribute/style/grammar/) and instructions on [building
the documentation](https://docs.docker.com/contribute/).
guide](https://docs.docker.com/opensource/doc-style) and instructions on [building
the documentation](https://docs.docker.com/opensource/project/test-and-docs/#build-and-test-the-documentation).
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
@ -134,41 +134,9 @@ committing your changes. Most editors have plug-ins that do this automatically.
Pull request descriptions should be as clear as possible and include a reference
to all the issues that they address.
Commit messages must be written in the imperative mood (max. 72 chars), followed
by an optional, more detailed explanatory text usually expanding on
why the work is necessary. The explanatory text should be separated by an
empty line.
The commit message *could* have a prefix scoping the change, however this is
not enforced. Common prefixes are `docs: <message>`, `vendor: <message>`,
`chore: <message>` or the package/area related to the change such as `pkg/foo: <message>`
or `telemetry: <message>`.
A standard commit.
```
Fix the exploding flux capacitor
A call to function A causes the flux capacitor to blow up every time
the sun and the moon align.
```
Using a package as prefix.
```
pkg/foo: prevent panic in flux capacitor
Calling function A causes the flux capacitor to blow up every time
the sun and the moon align.
```
Updating a specific vendored package.
```
vendor: github.com/docker/docker 6ac445c42bad (master, v28.0-dev)
```
Fixing a broken docs link.
```
docs: fix style/lint issues in deprecated.md
```
Commit messages must start with a capitalized and short summary (max. 50 chars)
written in the imperative, followed by an optional, more detailed explanatory
text which is separated from the summary by an empty line.
Code review comments may be added to your pull request. Discuss, then make the
suggested modifications and push additional commits to your feature branch. Post

View File

@ -1,43 +1,22 @@
# syntax=docker/dockerfile:1
ARG BASE_VARIANT=alpine
# 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 ALPINE_VERSION=3.20
ARG BASE_DEBIAN_DISTRO=bookworm
ARG GO_VERSION=1.25.5
# 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.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
ARG GO_VERSION=1.22.12
ARG XX_VERSION=1.6.1
ARG GOVERSIONINFO_VERSION=v1.3.0
ARG GOTESTSUM_VERSION=v1.10.0
ARG BUILDX_VERSION=0.20.0
ARG COMPOSE_VERSION=v2.32.4
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 git-daemon
RUN apk add --no-cache bash clang lld llvm file git
WORKDIR /go/src/github.com/docker/cli
FROM build-base-alpine AS build-alpine
@ -84,7 +63,7 @@ ARG PACKAGER_NAME
COPY --link --from=goversioninfo /out/goversioninfo /usr/bin/goversioninfo
RUN --mount=type=bind,target=.,ro \
--mount=type=cache,target=/root/.cache \
--mount=type=tmpfs,target=cmd/docker/winresources \
--mount=type=tmpfs,target=cli/winresources \
# override the default behavior of go with xx-go
xx-go --wrap && \
# export GOCACHE=$(go env GOCACHE)/$(xx-info)$([ -f /etc/alpine-release ] && echo "alpine") && \
@ -97,7 +76,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/|/cmd/docker-trust')
gotestsum -- -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/')
FROM scratch AS test-coverage
COPY --from=test /tmp/coverage.txt /coverage.txt
@ -122,6 +101,10 @@ 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/
@ -130,7 +113,7 @@ COPY --link --from=compose /docker-compose /usr/libexec/docker/cli-plugins/docke
COPY --link . .
ENV DOCKER_BUILDKIT=1
ENV PATH=/go/src/github.com/docker/cli/build:$PATH
CMD ["./scripts/test/e2e/entry"]
CMD ./scripts/test/e2e/entry
FROM build-base-${BASE_VARIANT} AS dev
COPY --link . .

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/|/cmd/docker-trust')} $(TESTFLAGS)
gotestsum -- $${TESTDIRS:-$(shell go list ./... | grep -vE '/vendor/|/e2e/')} $(TESTFLAGS)
.PHONY: test-coverage
test-coverage: ## run test coverage
mkdir -p $(CURDIR)/build/coverage
gotestsum -- $(shell go list ./... | grep -vE '/vendor/|/e2e/|/cmd/docker-trust') -coverprofile=$(CURDIR)/build/coverage/coverage.txt
gotestsum -- $(shell go list ./... | grep -vE '/vendor/|/e2e/') -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.24 . ; \
gofumpt -w -d -lang=1.21 . ; \
else \
go list -f {{.Dir}} ./... | xargs gofmt -w -s -d ; \
fi
@ -67,72 +67,46 @@ dynbinary: ## build dynamically linked binary
.PHONY: plugins
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
./scripts/build/plugins
.PHONY: vendor
vendor: ## update vendor with go modules
rm -rf vendor
scripts/with-go-mod.sh scripts/vendor update
./scripts/vendor update
.PHONY: validate-vendor
validate-vendor: ## validate vendor
scripts/with-go-mod.sh scripts/vendor validate
./scripts/vendor validate
.PHONY: mod-outdated
mod-outdated: ## check outdated dependencies
scripts/with-go-mod.sh scripts/vendor outdated
./scripts/vendor outdated
.PHONY: authors
authors: ## generate AUTHORS file from git history
scripts/docs/generate-authors.sh
.PHONY: completion
completion: shell-completion
completion: ## generate and install the shell-completion scripts
# Note: this uses system-wide paths, and so may overwrite completion
# scripts installed as part of deb/rpm packages.
#
# Given that this target is intended to debug/test updated versions, we could
# consider installing in per-user (~/.config, XDG_DATA_DIR) paths instead, but
# this will add more complexity.
#
# See https://github.com/docker/cli/pull/5770#discussion_r1927772710
install -D -p -m 0644 ./build/completion/bash/docker /usr/share/bash-completion/completions/docker
install -D -p -m 0644 ./build/completion/fish/docker.fish debian/docker-ce-cli/usr/share/fish/vendor_completions.d/docker.fish
install -D -p -m 0644 ./build/completion/zsh/_docker debian/docker-ce-cli/usr/share/zsh/vendor-completions/_docker
completion: binary
completion: /etc/bash_completion.d/docker
completion: ## generate and install the completion scripts
build/docker:
# This target is used by the "shell-completion" target, which requires either
# "binary" or "dynbinary" to have been built. We don't want to trigger those
# to prevent replacing a static binary with a dynamic one, or vice-versa.
@echo "Run 'make binary' or 'make dynbinary' first" && exit 1
.PHONY: shell-completion
shell-completion: build/docker # requires either "binary" or "dynbinary" to be built.
shell-completion: ## generate shell-completion scripts
@ ./scripts/build/shell-completion
.PHONY: /etc/bash_completion.d/docker
/etc/bash_completion.d/docker: ## generate and install the bash-completion script
mkdir -p /etc/bash_completion.d
docker completion bash > /etc/bash_completion.d/docker
.PHONY: manpages
manpages: ## generate man pages from go source and markdown
scripts/with-go-mod.sh scripts/docs/generate-man.sh
scripts/docs/generate-man.sh
.PHONY: mddocs
mddocs: ## generate markdown files from go source
scripts/with-go-mod.sh scripts/docs/generate-md.sh
scripts/docs/generate-md.sh
.PHONY: yamldocs
yamldocs: ## generate documentation YAML files consumed by docs repo
scripts/with-go-mod.sh scripts/docs/generate-yaml.sh
scripts/docs/generate-yaml.sh
.PHONY: help
help: ## print this help

View File

@ -1,10 +1,9 @@
# Docker CLI
[![PkgGoDev](https://pkg.go.dev/badge/github.com/docker/cli)](https://pkg.go.dev/github.com/docker/cli)
[![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/docker/cli)
[![Build Status](https://img.shields.io/github/actions/workflow/status/docker/cli/build.yml?branch=master&label=build&logo=github)](https://github.com/docker/cli/actions?query=workflow%3Abuild)
[![Test Status](https://img.shields.io/github/actions/workflow/status/docker/cli/test.yml?branch=master&label=test&logo=github)](https://github.com/docker/cli/actions?query=workflow%3Atest)
[![Go Report Card](https://goreportcard.com/badge/github.com/docker/cli)](https://goreportcard.com/report/github.com/docker/cli)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/docker/cli/badge)](https://scorecard.dev/viewer/?uri=github.com/docker/cli)
[![Codecov](https://img.shields.io/codecov/c/github/docker/cli?logo=codecov)](https://codecov.io/gh/docker/cli)
## About

View File

@ -1,44 +0,0 @@
# Security Policy
The maintainers of the Docker CLI take security seriously. If you discover
a security issue, please bring it to their attention right away!
## Reporting a Vulnerability
Please **DO NOT** file a public issue, instead send your report privately
to [security@docker.com](mailto:security@docker.com).
Reporter(s) can expect a response within 72 hours, acknowledging the issue was
received.
## Review Process
After receiving the report, an initial triage and technical analysis is
performed to confirm the report and determine its scope. We may request
additional information in this stage of the process.
Once a reviewer has confirmed the relevance of the report, a draft security
advisory will be created on GitHub. The draft advisory will be used to discuss
the issue with maintainers, the reporter(s), and where applicable, other
affected parties under embargo.
If the vulnerability is accepted, a timeline for developing a patch, public
disclosure, and patch release will be determined. If there is an embargo period
on public disclosure before the patch release, the reporter(s) are expected to
participate in the discussion of the timeline and abide by agreed upon dates
for public disclosure.
## Accreditation
Security reports are greatly appreciated and we will publicly thank you,
although we will keep your name confidential if you request it. We also like to
send gifts - if you're into swag, make sure to let us know. We do not currently
offer a paid security bounty program at this time.
## Supported Versions
This project uses long-lived branches to maintain releases, and follows
the maintenance cycle of the Moby project.
Refer to [BRANCHES-AND-TAGS.md](https://github.com/moby/moby/blob/master/project/BRANCHES-AND-TAGS.md)
in the default branch of the moby repository to learn about the current
maintenance status of each branch.

View File

@ -1 +1 @@
29.0.0-dev
27.3.1-dev

View File

@ -5,32 +5,31 @@ import (
"fmt"
"os"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
func main() {
plugin.Run(func(dockerCLI command.Cli) *cobra.Command {
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
goodbye := &cobra.Command{
Use: "goodbye",
Short: "Say Goodbye instead of Hello",
Run: func(cmd *cobra.Command, _ []string) {
_, _ = fmt.Fprintln(dockerCLI.Out(), "Goodbye World!")
fmt.Fprintln(dockerCli.Out(), "Goodbye World!")
},
}
apiversion := &cobra.Command{
Use: "apiversion",
Short: "Print the API version of the server",
RunE: func(_ *cobra.Command, _ []string) error {
apiClient := dockerCLI.Client()
ping, err := apiClient.Ping(context.Background(), client.PingOptions{})
cli := dockerCli.Client()
ping, err := cli.Ping(context.Background())
if err != nil {
return err
}
_, _ = fmt.Println(ping.APIVersion)
fmt.Println(ping.APIVersion)
return nil
},
}
@ -39,7 +38,7 @@ func main() {
Use: "exitstatus2",
Short: "Exit with status 2",
RunE: func(_ *cobra.Command, _ []string) error {
_, _ = fmt.Fprintln(dockerCLI.Err(), "Exiting with error status 2")
fmt.Fprintln(dockerCli.Err(), "Exiting with error status 2")
os.Exit(2)
return nil
},
@ -57,33 +56,33 @@ func main() {
return err
}
if preRun {
_, _ = fmt.Fprintln(dockerCLI.Err(), "Plugin PersistentPreRunE called")
fmt.Fprintf(dockerCli.Err(), "Plugin PersistentPreRunE called")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if debug {
_, _ = fmt.Fprintln(dockerCLI.Err(), "Plugin debug mode enabled")
fmt.Fprintf(dockerCli.Err(), "Plugin debug mode enabled")
}
switch optContext {
case "Christmas":
_, _ = fmt.Fprintln(dockerCLI.Out(), "Merry Christmas!")
fmt.Fprintf(dockerCli.Out(), "Merry Christmas!\n")
return nil
case "":
// nothing
}
if who == "" {
who, _ = dockerCLI.ConfigFile().PluginConfig("helloworld", "who")
who, _ = dockerCli.ConfigFile().PluginConfig("helloworld", "who")
}
if who == "" {
who = "World"
}
_, _ = fmt.Fprintln(dockerCLI.Out(), "Hello", who)
dockerCLI.ConfigFile().SetPluginConfig("helloworld", "lastwho", who)
return dockerCLI.ConfigFile().Save()
fmt.Fprintf(dockerCli.Out(), "Hello %s!\n", who)
dockerCli.ConfigFile().SetPluginConfig("helloworld", "lastwho", who)
return dockerCli.ConfigFile().Save()
},
}
@ -98,7 +97,7 @@ func main() {
cmd.AddCommand(goodbye, apiversion, exitStatus2)
return cmd
},
metadata.Metadata{
manager.Metadata{
SchemaVersion: "0.1.0",
Vendor: "Docker Inc.",
Version: "testing",

View File

@ -11,8 +11,8 @@ func PrintNextSteps(out io.Writer, messages []string) {
if len(messages) == 0 {
return
}
_, _ = fmt.Fprintln(out, aec.Bold.Apply("\nWhat's next:"))
fmt.Fprintln(out, aec.Bold.Apply("\nWhat's next:"))
for _, n := range messages {
_, _ = fmt.Fprintln(out, " ", n)
_, _ = fmt.Fprintf(out, " %s\n", n)
}
}

View File

@ -1,10 +1,12 @@
package manager
import (
"os/exec"
import "os/exec"
"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
@ -15,5 +17,5 @@ func (c *candidate) Path() string {
}
func (c *candidate) Metadata() ([]byte, error) {
return exec.Command(c.path, metadata.MetadataSubcommandName).Output() // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
return exec.Command(c.path, MetadataSubcommandName).Output() // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
}

View File

@ -6,10 +6,9 @@ import (
"strings"
"testing"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/assert/cmp"
)
type fakeCandidate struct {
@ -31,132 +30,62 @@ func (c *fakeCandidate) Metadata() ([]byte, error) {
func TestValidateCandidate(t *testing.T) {
const (
goodPluginName = metadata.NamePrefix + "goodplugin"
builtinName = metadata.NamePrefix + "builtin"
builtinAlias = metadata.NamePrefix + "alias"
goodPluginName = NamePrefix + "goodplugin"
badPrefixPath = "/usr/local/libexec/cli-plugins/wobble"
badNamePath = "/usr/local/libexec/cli-plugins/docker-123456"
goodPluginPath = "/usr/local/libexec/cli-plugins/" + goodPluginName
builtinName = NamePrefix + "builtin"
builtinAlias = 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}`
)
fakeroot := &cobra.Command{Use: "docker"}
fakeroot.AddCommand(&cobra.Command{
Use: strings.TrimPrefix(builtinName, metadata.NamePrefix),
Use: strings.TrimPrefix(builtinName, NamePrefix),
Aliases: []string{
strings.TrimPrefix(builtinAlias, metadata.NamePrefix),
strings.TrimPrefix(builtinAlias, NamePrefix),
},
})
for _, tc := range []struct {
name string
plugin *fakeCandidate
name string
c *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
}{
// 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",
},
/* 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", 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}},
} {
t.Run(tc.name, func(t *testing.T) {
p, err := newPlugin(tc.plugin, fakeroot.Commands())
p, err := newPlugin(tc.c, fakeroot.Commands())
switch {
case tc.err != "":
assert.ErrorContains(t, err, tc.err)
case tc.invalid != "":
assert.NilError(t, err)
assert.Assert(t, is.ErrorType(p.Err, reflect.TypeOf(&pluginError{})))
assert.Assert(t, cmp.ErrorType(p.Err, reflect.TypeOf(&pluginError{})))
assert.ErrorContains(t, p.Err, tc.invalid)
default:
assert.NilError(t, err)
assert.Equal(t, metadata.NamePrefix+p.Name, goodPluginName)
assert.Equal(t, p.SchemaVersion, tc.expVer)
assert.Equal(t, NamePrefix+p.Name, goodPluginName)
assert.Equal(t, p.SchemaVersion, "0.1.0")
assert.Equal(t, p.Vendor, "e2e-testing")
}
})

View File

@ -2,12 +2,41 @@ package manager
import (
"fmt"
"net/url"
"os"
"strings"
"sync"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel/attribute"
)
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 = "com.docker.cli.plugin"
// CommandAnnotationPluginVendor is added to every stub command
// added by AddPluginCommandStubs and contains the vendor of
// that plugin.
CommandAnnotationPluginVendor = "com.docker.cli.plugin.vendor"
// CommandAnnotationPluginVersion is added to every stub command
// added by AddPluginCommandStubs and contains the version of
// that plugin.
CommandAnnotationPluginVersion = "com.docker.cli.plugin.version"
// 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 = "com.docker.cli.plugin-invalid"
// CommandAnnotationPluginCommandPath is added to overwrite the
// command path for a plugin invocation.
CommandAnnotationPluginCommandPath = "com.docker.cli.plugin.command_path"
)
var pluginCommandStubsOnce sync.Once
@ -15,30 +44,30 @@ var pluginCommandStubsOnce sync.Once
// AddPluginCommandStubs adds a stub cobra.Commands for each valid and invalid
// plugin. The command stubs will have several annotations added, see
// `CommandAnnotationPlugin*`.
func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (err error) {
func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err error) {
pluginCommandStubsOnce.Do(func() {
var plugins []Plugin
plugins, err = ListPlugins(dockerCLI, rootCmd)
plugins, err = ListPlugins(dockerCli, rootCmd)
if err != nil {
return
}
for _, p := range plugins {
p := p
vendor := p.Vendor
if vendor == "" {
vendor = "unknown"
}
annotations := map[string]string{
metadata.CommandAnnotationPlugin: "true",
metadata.CommandAnnotationPluginVendor: vendor,
metadata.CommandAnnotationPluginVersion: p.Version,
CommandAnnotationPlugin: "true",
CommandAnnotationPluginVendor: vendor,
CommandAnnotationPluginVersion: p.Version,
}
if p.Err != nil {
annotations[metadata.CommandAnnotationPluginInvalid] = p.Err.Error()
annotations[CommandAnnotationPluginInvalid] = p.Err.Error()
}
rootCmd.AddCommand(&cobra.Command{
Use: p.Name,
Short: p.ShortDescription,
Hidden: p.Hidden,
Run: func(_ *cobra.Command, _ []string) {},
Annotations: annotations,
DisableFlagParsing: true,
@ -53,7 +82,7 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
cmd.HelpFunc()(rootCmd, args)
return nil
}
return fmt.Errorf("docker: unknown command: docker %s\n\nRun 'docker --help' for more information", cmd.Name())
return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'", cmd.Name())
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Delegate completion to plugin
@ -61,7 +90,7 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
cargs = append(cargs, args...)
cargs = append(cargs, toComplete)
os.Args = cargs
runCommand, runErr := PluginRunCommand(dockerCLI, p.Name, cmd)
runCommand, runErr := PluginRunCommand(dockerCli, p.Name, cmd)
if runErr != nil {
return nil, cobra.ShellCompDirectiveError
}
@ -76,3 +105,44 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
})
return err
}
const (
dockerCliAttributePrefix = attribute.Key("docker.cli")
cobraCommandPath = attribute.Key("cobra.command_path")
)
func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Set {
commandPath := cmd.Annotations[CommandAnnotationPluginCommandPath]
if commandPath == "" {
commandPath = fmt.Sprintf("%s %s", cmd.CommandPath(), plugin.Name)
}
attrSet := attribute.NewSet(
cobraCommandPath.String(commandPath),
)
kvs := make([]attribute.KeyValue, 0, attrSet.Len())
for iter := attrSet.Iter(); iter.Next(); {
attr := iter.Attribute()
kvs = append(kvs, attribute.KeyValue{
Key: dockerCliAttributePrefix + "." + attr.Key,
Value: attr.Value,
})
}
return attribute.NewSet(kvs...)
}
func appendPluginResourceAttributesEnvvar(env []string, cmd *cobra.Command, plugin Plugin) []string {
if attrs := getPluginResourceAttributes(cmd, plugin); attrs.Len() > 0 {
// values in environment variables need to be in baggage format
// otel/baggage package can be used after update to v1.22, currently it encodes incorrectly
attrsSlice := make([]string, attrs.Len())
for iter := attrs.Iter(); iter.Next(); {
i, v := iter.IndexedAttribute()
attrsSlice[i] = string(v.Key) + "=" + url.PathEscape(v.Value.AsString())
}
env = append(env, ResourceAttributesEnvvar+"="+strings.Join(attrsSlice, ","))
}
return env
}

View File

@ -1,26 +0,0 @@
package manager
import (
"testing"
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
)
func TestPluginResourceAttributesEnvvar(t *testing.T) {
cmd := &cobra.Command{
Annotations: map[string]string{
cobra.CommandDisplayNameAnnotation: "docker",
},
}
// Ensure basic usage is fine.
env := appendPluginResourceAttributesEnvvar(nil, cmd, Plugin{Name: "compose"})
assert.DeepEqual(t, []string{"OTEL_RESOURCE_ATTRIBUTES=docker.cli.cobra.command_path=docker%20compose"}, env)
// Add a user-based environment variable to OTEL_RESOURCE_ATTRIBUTES.
t.Setenv("OTEL_RESOURCE_ATTRIBUTES", "a.b.c=foo")
env = appendPluginResourceAttributesEnvvar(nil, cmd, Plugin{Name: "compose"})
assert.DeepEqual(t, []string{"OTEL_RESOURCE_ATTRIBUTES=a.b.c=foo,docker.cli.cobra.command_path=docker%20compose"}, env)
}

View File

@ -1,10 +1,10 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.22
package manager
import (
"fmt"
"github.com/pkg/errors"
)
// pluginError is set as Plugin.Err by NewPlugin if the plugin
@ -23,6 +23,11 @@ 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
@ -34,13 +39,16 @@ func (e *pluginError) MarshalText() (text []byte, err error) {
}
// wrapAsPluginError wraps an error in a pluginError with an
// additional message.
// additional message, analogous to errors.Wrapf.
func wrapAsPluginError(err error, msg string) error {
return &pluginError{cause: fmt.Errorf("%s: %w", msg, err)}
if err == nil {
return nil
}
return &pluginError{cause: errors.Wrap(err, msg)}
}
// newPluginError creates a new pluginError, analogous to
// NewPluginError creates a new pluginError, analogous to
// errors.Errorf.
func newPluginError(msg string, args ...any) error {
return &pluginError{cause: fmt.Errorf(msg, args...)}
func NewPluginError(msg string, args ...any) error {
return &pluginError{cause: errors.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,7 +21,4 @@ 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

@ -6,8 +6,7 @@ import (
"strings"
"github.com/docker/cli/cli-plugins/hooks"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/command"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -30,28 +29,29 @@ type HookPluginData struct {
// a main CLI command was executed. It calls the hook subcommand for all
// present CLI plugins that declare support for hooks in their metadata and
// parses/prints their responses.
func RunCLICommandHooks(ctx context.Context, dockerCLI config.Provider, rootCmd, subCommand *cobra.Command, cmdErrorMessage string) {
func RunCLICommandHooks(ctx context.Context, dockerCli command.Cli, rootCmd, subCommand *cobra.Command, cmdErrorMessage string) {
commandName := strings.TrimPrefix(subCommand.CommandPath(), rootCmd.Name()+" ")
flags := getCommandFlags(subCommand)
runHooks(ctx, dockerCLI.ConfigFile(), rootCmd, subCommand, commandName, flags, cmdErrorMessage)
runHooks(ctx, dockerCli, rootCmd, subCommand, commandName, flags, cmdErrorMessage)
}
// RunPluginHooks is the entrypoint for the hooks execution flow
// after a plugin command was just executed by the CLI.
func RunPluginHooks(ctx context.Context, dockerCLI config.Provider, rootCmd, subCommand *cobra.Command, args []string) {
func RunPluginHooks(ctx context.Context, dockerCli command.Cli, rootCmd, subCommand *cobra.Command, args []string) {
commandName := strings.Join(args, " ")
flags := getNaiveFlags(args)
runHooks(ctx, dockerCLI.ConfigFile(), rootCmd, subCommand, commandName, flags, "")
runHooks(ctx, dockerCli, rootCmd, subCommand, commandName, flags, "")
}
func runHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) {
nextSteps := invokeAndCollectHooks(ctx, cfg, rootCmd, subCommand, invokedCommand, flags, cmdErrorMessage)
hooks.PrintNextSteps(subCommand.ErrOrStderr(), nextSteps)
func runHooks(ctx context.Context, dockerCli command.Cli, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) {
nextSteps := invokeAndCollectHooks(ctx, dockerCli, rootCmd, subCommand, invokedCommand, flags, cmdErrorMessage)
hooks.PrintNextSteps(dockerCli.Err(), nextSteps)
}
func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []string {
func invokeAndCollectHooks(ctx context.Context, dockerCli command.Cli, rootCmd, subCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []string {
// check if the context was cancelled before invoking hooks
select {
case <-ctx.Done():
@ -59,20 +59,19 @@ func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, root
default:
}
pluginsCfg := cfg.Plugins
pluginsCfg := dockerCli.ConfigFile().Plugins
if pluginsCfg == nil {
return nil
}
pluginDirs := getPluginDirs(cfg)
nextSteps := make([]string, 0, len(pluginsCfg))
for pluginName, pluginCfg := range pluginsCfg {
match, ok := pluginMatch(pluginCfg, subCmdStr)
for pluginName, cfg := range pluginsCfg {
match, ok := pluginMatch(cfg, subCmdStr)
if !ok {
continue
}
p, err := getPlugin(pluginName, pluginDirs, rootCmd)
p, err := GetPlugin(pluginName, dockerCli, rootCmd)
if err != nil {
continue
}

View File

@ -2,7 +2,6 @@ package manager
import (
"context"
"errors"
"os"
"os/exec"
"path/filepath"
@ -10,25 +9,46 @@ import (
"strings"
"sync"
"github.com/containerd/errdefs"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/command"
"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 = "DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND"
// ResourceAttributesEnvvar is the name of the envvar that includes additional
// resource attributes for OTEL.
ResourceAttributesEnvvar = "OTEL_RESOURCE_ATTRIBUTES"
)
// errPluginNotFound is the error returned when a plugin could not be found.
type errPluginNotFound string
func (errPluginNotFound) NotFound() {}
func (e errPluginNotFound) NotFound() {}
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.
//
@ -39,16 +59,20 @@ func (e errPluginNotFound) Error() string {
// 3. Platform-specific defaultSystemPluginDirs.
//
// [ConfigFile.CLIPluginsExtraDirs]: https://pkg.go.dev/github.com/docker/cli@v26.1.4+incompatible/cli/config/configfile#ConfigFile.CLIPluginsExtraDirs
func getPluginDirs(cfg *configfile.ConfigFile) []string {
func getPluginDirs(cfg *configfile.ConfigFile) ([]string, error) {
var pluginDirs []string
if cfg != nil {
pluginDirs = append(pluginDirs, cfg.CLIPluginsExtraDirs...)
}
pluginDir := filepath.Join(config.Dir(), "cli-plugins")
pluginDir, err := config.Path("cli-plugins")
if err != nil {
return nil, err
}
pluginDirs = append(pluginDirs, pluginDir)
pluginDirs = append(pluginDirs, defaultSystemPluginDirs...)
return pluginDirs
return pluginDirs, nil
}
func addPluginCandidatesFromDir(res map[string][]string, d string) {
@ -59,26 +83,18 @@ func addPluginCandidatesFromDir(res map[string][]string, d string) {
return
}
for _, dentry := range dentries {
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
switch dentry.Type() & os.ModeType {
case 0, os.ModeSymlink:
// Regular file or symlink, keep going
default:
// Something else, ignore.
continue
}
name := dentry.Name()
if !strings.HasPrefix(name, metadata.NamePrefix) {
if !strings.HasPrefix(name, NamePrefix) {
continue
}
name = strings.TrimPrefix(name, metadata.NamePrefix)
name = strings.TrimPrefix(name, NamePrefix)
var err error
if name, err = trimExeSuffix(name); err != nil {
continue
@ -97,12 +113,12 @@ func listPluginCandidates(dirs []string) map[string][]string {
}
// GetPlugin returns a plugin on the system by its name
func GetPlugin(name string, dockerCLI config.Provider, rootcmd *cobra.Command) (*Plugin, error) {
pluginDirs := getPluginDirs(dockerCLI.ConfigFile())
return getPlugin(name, pluginDirs, rootcmd)
}
func GetPlugin(name string, dockerCli command.Cli, rootcmd *cobra.Command) (*Plugin, error) {
pluginDirs, err := getPluginDirs(dockerCli.ConfigFile())
if err != nil {
return nil, err
}
func getPlugin(name string, pluginDirs []string, rootcmd *cobra.Command) (*Plugin, error) {
candidates := listPluginCandidates(pluginDirs)
if paths, ok := candidates[name]; ok {
if len(paths) == 0 {
@ -113,7 +129,7 @@ func getPlugin(name string, pluginDirs []string, rootcmd *cobra.Command) (*Plugi
if err != nil {
return nil, err
}
if !errdefs.IsNotFound(p.Err) {
if !IsNotFound(p.Err) {
p.ShadowedPaths = paths[1:]
}
return &p, nil
@ -123,21 +139,17 @@ func getPlugin(name string, pluginDirs []string, rootcmd *cobra.Command) (*Plugi
}
// ListPlugins produces a list of the plugins available on the system
func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, error) {
pluginDirs := getPluginDirs(dockerCli.ConfigFile())
candidates := listPluginCandidates(pluginDirs)
if len(candidates) == 0 {
return nil, nil
func ListPlugins(dockerCli command.Cli, rootcmd *cobra.Command) ([]Plugin, error) {
pluginDirs, err := getPluginDirs(dockerCli.ConfigFile())
if err != nil {
return nil, err
}
candidates := listPluginCandidates(pluginDirs)
var plugins []Plugin
var mu sync.Mutex
ctx := rootcmd.Context()
if ctx == nil {
// Fallback, mostly for tests that pass a bare cobra.command
ctx = context.Background()
}
eg, _ := errgroup.WithContext(ctx)
eg, _ := errgroup.WithContext(context.TODO())
cmds := rootcmd.Commands()
for _, paths := range candidates {
func(paths []string) {
@ -150,7 +162,7 @@ func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, e
if err != nil {
return err
}
if !errdefs.IsNotFound(p.Err) {
if !IsNotFound(p.Err) {
p.ShadowedPaths = paths[1:]
mu.Lock()
defer mu.Unlock()
@ -171,21 +183,24 @@ func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, e
return plugins, nil
}
// PluginRunCommand returns an [os/exec.Cmd] which when [os/exec.Cmd.Run] will execute the named plugin.
// PluginRunCommand returns an "os/exec".Cmd which when .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 [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) {
// The error returned satisfies the IsNotFound() predicate if no plugin was found or if the first candidate plugin was invalid somehow.
func PluginRunCommand(dockerCli command.Cli, 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 !isValidPluginName(name) {
if !pluginNameRe.MatchString(name) {
// We treat this as "not found" so that callers will
// fallback to their "invalid" command path.
return nil, errPluginNotFound(name)
}
exename := addExeSuffix(metadata.NamePrefix + name)
pluginDirs := getPluginDirs(dockerCli.ConfigFile())
exename := addExeSuffix(NamePrefix + name)
pluginDirs, err := getPluginDirs(dockerCli.ConfigFile())
if err != nil {
return nil, err
}
for _, d := range pluginDirs {
path := filepath.Join(d, exename)
@ -218,7 +233,7 @@ func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Com
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(cmd.Environ(), metadata.ReexecEnvvar+"="+os.Args[0])
cmd.Env = append(cmd.Environ(), ReexecEnvvar+"="+os.Args[0])
cmd.Env = appendPluginResourceAttributesEnvvar(cmd.Env, rootcmd, plugin)
return cmd, nil
@ -228,5 +243,5 @@ func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Com
// IsPluginCommand checks if the given cmd is a plugin-stub.
func IsPluginCommand(cmd *cobra.Command) bool {
return cmd.Annotations[metadata.CommandAnnotationPlugin] == "true"
return cmd.Annotations[CommandAnnotationPlugin] == "true"
}

View File

@ -1,11 +1,9 @@
package manager
import (
"path/filepath"
"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"
@ -38,7 +36,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 ignored
fs.WithSymlink("docker-brokensymlink", "broken"), // A broken symlink is still a candidate (but would fail tests later)
fs.WithFile("non-plugin-symlinked", ""), // This shouldn't appear, but ...
fs.WithSymlink("docker-symlinked", "non-plugin-symlinked"), // ... this link to it should.
),
@ -72,6 +70,9 @@ func TestListPluginCandidates(t *testing.T) {
"hardlink2": {
dir.Join("plugins2", "docker-hardlink2"),
},
"brokensymlink": {
dir.Join("plugins3", "docker-brokensymlink"),
},
"symlinked": {
dir.Join("plugins3", "docker-symlinked"),
},
@ -80,12 +81,6 @@ func TestListPluginCandidates(t *testing.T) {
assert.DeepEqual(t, candidates, exp)
}
func TestListPluginCandidatesEmpty(t *testing.T) {
tmpDir := t.TempDir()
candidates := listPluginCandidates([]string{tmpDir, filepath.Join(tmpDir, "no-such-dir")})
assert.Assert(t, len(candidates) == 0)
}
// Regression test for https://github.com/docker/cli/issues/5643.
// Check that inaccessible directories that come before accessible ones are ignored
// and do not prevent the latter from being processed.
@ -129,7 +124,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, errdefs.IsNotFound(err))
assert.Assert(t, IsNotFound(err))
}
func TestListPluginsIsSorted(t *testing.T) {
@ -164,18 +159,21 @@ 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, errdefs.IsNotFound(err))
assert.Assert(t, !errdefs.IsNotFound(nil))
assert.Assert(t, IsNotFound(err))
assert.Assert(t, !IsNotFound(nil))
}
func TestGetPluginDirs(t *testing.T) {
cli := test.NewFakeCli(nil)
pluginDir := filepath.Join(config.Dir(), "cli-plugins")
pluginDir, err := config.Path("cli-plugins")
assert.NilError(t, err)
expected := append([]string{pluginDir}, defaultSystemPluginDirs...)
pluginDirs := getPluginDirs(cli.ConfigFile())
var pluginDirs []string
pluginDirs, err = getPluginDirs(cli.ConfigFile())
assert.Equal(t, strings.Join(expected, ":"), strings.Join(pluginDirs, ":"))
assert.NilError(t, err)
extras := []string{
"foo", "bar", "baz",
@ -184,6 +182,7 @@ func TestGetPluginDirs(t *testing.T) {
cli.SetConfigFile(&configfile.ConfigFile{
CLIPluginsExtraDirs: extras,
})
pluginDirs = getPluginDirs(cli.ConfigFile())
pluginDirs, err = getPluginDirs(cli.ConfigFile())
assert.DeepEqual(t, expected, pluginDirs)
assert.NilError(t, err)
}

View File

@ -1,4 +1,4 @@
package metadata
package manager
const (
// NamePrefix is the prefix required on all plugin binary names
@ -13,12 +13,6 @@ const (
// which must be implemented by plugins declaring support
// for hooks in their metadata.
HookSubcommandName = "docker-cli-plugin-hooks"
// 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 = "DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND"
)
// Metadata provided by the plugin.
@ -33,6 +27,4 @@ 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

@ -2,23 +2,22 @@ package manager
import (
"context"
"encoding"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"regexp"
"strings"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var pluginNameRe = regexp.MustCompile("^[a-z][a-z0-9]*$")
// Plugin represents a potential plugin with all it's metadata.
type Plugin struct {
metadata.Metadata
Metadata
Name string `json:",omitempty"`
Path string `json:",omitempty"`
@ -30,34 +29,12 @@ 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 pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
path := c.Path()
if path == "" {
return Plugin{}, errors.New("plugin candidate path cannot be empty")
@ -67,24 +44,24 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
// which would fail here, so there are all real errors.
fullname := filepath.Base(path)
if fullname == "." {
return Plugin{}, fmt.Errorf("unable to determine basename of plugin candidate %q", path)
return Plugin{}, errors.Errorf("unable to determine basename of plugin candidate %q", path)
}
var err error
if fullname, err = trimExeSuffix(fullname); err != nil {
return Plugin{}, fmt.Errorf("plugin candidate %q: %w", path, err)
return Plugin{}, errors.Wrapf(err, "plugin candidate %q", path)
}
if !strings.HasPrefix(fullname, metadata.NamePrefix) {
return Plugin{}, fmt.Errorf("plugin candidate %q: does not have %q prefix", path, metadata.NamePrefix)
if !strings.HasPrefix(fullname, NamePrefix) {
return Plugin{}, errors.Errorf("plugin candidate %q: does not have %q prefix", path, NamePrefix)
}
p := Plugin{
Name: strings.TrimPrefix(fullname, metadata.NamePrefix),
Name: strings.TrimPrefix(fullname, NamePrefix),
Path: path,
}
// Now apply the candidate tests, so these update p.Err.
if !isValidPluginName(p.Name) {
p.Err = newPluginError("plugin candidate %q did not match %q", p.Name, pluginNameFormat)
if !pluginNameRe.MatchString(p.Name) {
p.Err = NewPluginError("plugin candidate %q did not match %q", p.Name, pluginNameRe.String())
return p, nil
}
@ -96,11 +73,11 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
continue
}
if cmd.Name() == p.Name {
p.Err = newPluginError("plugin %q duplicates builtin command", p.Name)
p.Err = NewPluginError("plugin %q duplicates builtin command", p.Name)
return p, nil
}
if cmd.HasAlias(p.Name) {
p.Err = newPluginError("plugin %q duplicates an alias of builtin command %q", p.Name, cmd.Name())
p.Err = NewPluginError("plugin %q duplicates an alias of builtin command %q", p.Name, cmd.Name())
return p, nil
}
}
@ -116,42 +93,17 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
p.Err = wrapAsPluginError(err, "invalid metadata")
return p, nil
}
if err := validateSchemaVersion(p.Metadata.SchemaVersion); err != nil {
p.Err = &pluginError{cause: err}
if p.Metadata.SchemaVersion != "0.1.0" {
p.Err = NewPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
return p, nil
}
if p.Metadata.Vendor == "" {
p.Err = newPluginError("plugin metadata does not define a vendor")
p.Err = NewPluginError("plugin metadata does not define a vendor")
return p, nil
}
return p, nil
}
// 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) {
@ -160,9 +112,9 @@ func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte,
return nil, wrapAsPluginError(err, "failed to marshall hook data")
}
pCmd := exec.CommandContext(ctx, p.Path, p.Name, metadata.HookSubcommandName, string(hDataBytes)) // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
pCmd := exec.CommandContext(ctx, p.Path, p.Name, HookSubcommandName, string(hDataBytes)) // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
pCmd.Env = os.Environ()
pCmd.Env = append(pCmd.Env, metadata.ReexecEnvvar+"="+os.Args[0])
pCmd.Env = append(pCmd.Env, ReexecEnvvar+"="+os.Args[0])
hookCmdOutput, err := pCmd.Output()
if err != nil {
return nil, wrapAsPluginError(err, "failed to execute plugin hook subcommand")
@ -170,26 +122,3 @@ 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

@ -1,43 +0,0 @@
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

@ -1,16 +1,22 @@
package manager
import (
"fmt"
"path/filepath"
"strings"
"github.com/pkg/errors"
)
// This is made slightly more complex due to needing to be case-insensitive.
// This is made slightly more complex due to needing to be case insensitive.
func trimExeSuffix(s string) (string, error) {
ext := filepath.Ext(s)
if ext == "" || !strings.EqualFold(ext, ".exe") {
return "", fmt.Errorf("path %q lacks required file extension (.exe)", s)
if ext == "" {
return "", errors.Errorf("path %q lacks required file extension", s)
}
exe := ".exe"
if !strings.EqualFold(ext, exe) {
return "", errors.Errorf("path %q lacks required %q suffix", s, exe)
}
return strings.TrimSuffix(s, ext), nil
}

View File

@ -1,85 +0,0 @@
package manager
import (
"fmt"
"os"
"strings"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/baggage"
)
const (
// resourceAttributesEnvVar is the name of the envvar that includes additional
// resource attributes for OTEL as defined in the [OpenTelemetry specification].
//
// [OpenTelemetry specification]: https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration
resourceAttributesEnvVar = "OTEL_RESOURCE_ATTRIBUTES"
// dockerCLIAttributePrefix is the prefix for any docker cli OTEL attributes.
//
// It is a copy of the const defined in [command.dockerCLIAttributePrefix].
dockerCLIAttributePrefix = "docker.cli."
cobraCommandPath = attribute.Key("cobra.command_path")
)
func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Set {
commandPath := cmd.Annotations[metadata.CommandAnnotationPluginCommandPath]
if commandPath == "" {
commandPath = fmt.Sprintf("%s %s", cmd.CommandPath(), plugin.Name)
}
attrSet := attribute.NewSet(
cobraCommandPath.String(commandPath),
)
kvs := make([]attribute.KeyValue, 0, attrSet.Len())
for iter := attrSet.Iter(); iter.Next(); {
attr := iter.Attribute()
kvs = append(kvs, attribute.KeyValue{
Key: dockerCLIAttributePrefix + attr.Key,
Value: attr.Value,
})
}
return attribute.NewSet(kvs...)
}
func appendPluginResourceAttributesEnvvar(env []string, cmd *cobra.Command, plugin Plugin) []string {
if attrs := getPluginResourceAttributes(cmd, plugin); attrs.Len() > 0 {
// Construct baggage members for each of the attributes.
// Ignore any failures as these aren't significant and
// represent an internal issue.
members := make([]baggage.Member, 0, attrs.Len())
for iter := attrs.Iter(); iter.Next(); {
attr := iter.Attribute()
m, err := baggage.NewMemberRaw(string(attr.Key), attr.Value.AsString())
if err != nil {
otel.Handle(err)
continue
}
members = append(members, m)
}
// Combine plugin added resource attributes with ones found in the environment
// variable. Our own attributes should be namespaced so there shouldn't be a
// conflict. We do not parse the environment variable because we do not want
// to handle errors in user configuration.
attrsSlice := make([]string, 0, 2)
if v := strings.TrimSpace(os.Getenv(resourceAttributesEnvVar)); v != "" {
attrsSlice = append(attrsSlice, v)
}
if b, err := baggage.New(members...); err != nil {
otel.Handle(err)
} else if b.Len() > 0 {
attrsSlice = append(attrsSlice, b.String())
}
if len(attrsSlice) > 0 {
env = append(env, resourceAttributesEnvVar+"="+strings.Join(attrsSlice, ","))
}
}
return env
}

View File

@ -1,28 +0,0 @@
package 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 = "com.docker.cli.plugin"
// CommandAnnotationPluginVendor is added to every stub command
// added by AddPluginCommandStubs and contains the vendor of
// that plugin.
CommandAnnotationPluginVendor = "com.docker.cli.plugin.vendor"
// CommandAnnotationPluginVersion is added to every stub command
// added by AddPluginCommandStubs and contains the version of
// that plugin.
CommandAnnotationPluginVersion = "com.docker.cli.plugin.version"
// 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 = "com.docker.cli.plugin-invalid"
// CommandAnnotationPluginCommandPath is added to overwrite the
// command path for a plugin invocation.
CommandAnnotationPluginCommandPath = "com.docker.cli.plugin.command_path"
)

View File

@ -3,18 +3,17 @@ package plugin
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"sync"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/socket"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/connhelper"
"github.com/docker/cli/cli/debug"
"github.com/moby/moby/client"
"github.com/docker/docker/client"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel"
)
@ -30,12 +29,12 @@ import (
var PersistentPreRunE func(*cobra.Command, []string) error
// RunPlugin executes the specified plugin command
func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadata.Metadata) error {
func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager.Metadata) error {
tcmd := newPluginCommand(dockerCli, plugin, meta)
var persistentPreRunOnce sync.Once
PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
var retErr error
var err error
persistentPreRunOnce.Do(func() {
ctx, cancel := context.WithCancel(cmd.Context())
cmd.SetContext(ctx)
@ -47,7 +46,7 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadat
opts = append(opts, withPluginClientConn(plugin.Name()))
}
opts = append(opts, command.WithEnableGlobalMeterProvider(), command.WithEnableGlobalTracerProvider())
retErr = tcmd.Initialize(opts...)
err = tcmd.Initialize(opts...)
ogRunE := cmd.RunE
if ogRunE == nil {
ogRun := cmd.Run
@ -67,7 +66,7 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadat
return err
}
})
return retErr
return err
}
cmd, args, err := tcmd.HandleGlobalFlags()
@ -80,42 +79,39 @@ 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 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) {
// 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 manager.Metadata) {
otel.SetErrorHandler(debug.OTELErrorHandler)
dockerCLI, err := command.NewDockerCli(ops...)
dockerCli, err := command.NewDockerCli()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
plugin := makeCmd(dockerCLI)
plugin := makeCmd(dockerCli)
if err := RunPlugin(dockerCLI, plugin, meta); err != nil {
var stErr cli.StatusError
if errors.As(err, &stErr) {
if err := RunPlugin(dockerCli, plugin, meta); err != nil {
if sterr, ok := err.(cli.StatusError); ok {
if sterr.Status != "" {
fmt.Fprintln(dockerCli.Err(), sterr.Status)
}
// StatusError should only be used for errors, and all errors should
// have a non-zero exit status, so never exit with 0
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
if sterr.StatusCode == 0 {
os.Exit(1)
}
_, _ = fmt.Fprintln(dockerCLI.Err(), stErr)
os.Exit(stErr.StatusCode)
os.Exit(sterr.StatusCode)
}
_, _ = fmt.Fprintln(dockerCLI.Err(), err)
fmt.Fprintln(dockerCli.Err(), err)
os.Exit(1)
}
}
func withPluginClientConn(name string) command.CLIOption {
return func(cli *command.DockerCli) error {
return command.WithInitializeClient(func(dockerCli *command.DockerCli) (client.APIClient, error) {
cmd := "docker"
if x := os.Getenv(metadata.ReexecEnvvar); x != "" {
if x := os.Getenv(manager.ReexecEnvvar); x != "" {
cmd = x
}
var flags []string
@ -137,19 +133,16 @@ func withPluginClientConn(name string) command.CLIOption {
helper, err := connhelper.GetCommandConnectionHelper(cmd, flags...)
if err != nil {
return err
return nil, err
}
apiClient, err := client.New(client.WithDialContext(helper.Dialer))
if err != nil {
return err
}
return command.WithAPIClient(apiClient)(cli)
}
return client.NewClientWithOpts(client.WithDialContext(helper.Dialer))
})
}
func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadata.Metadata) *cli.TopLevelCommand {
func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager.Metadata) *cli.TopLevelCommand {
name := plugin.Name()
fullname := metadata.NamePrefix + name
fullname := manager.NamePrefix + name
cmd := &cobra.Command{
Use: fmt.Sprintf("docker [OPTIONS] %s [ARG...]", name),
@ -165,14 +158,9 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta
CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: false,
HiddenDefaultCmd: true,
DisableDescriptions: os.Getenv("DOCKER_CLI_DISABLE_COMPLETION_DESCRIPTION") != "",
DisableDescriptions: true,
},
}
// 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())
@ -184,30 +172,17 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta
newMetadataSubcommand(plugin, meta),
)
visitAll(cmd,
// prevent adding "[flags]" to the end of the usage line.
func(c *cobra.Command) { c.DisableFlagsInUseLine = true },
)
cli.DisableFlagsInUseLine(cmd)
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 {
func newMetadataSubcommand(plugin *cobra.Command, meta manager.Metadata) *cobra.Command {
if meta.ShortDescription == "" {
meta.ShortDescription = plugin.Short
}
cmd := &cobra.Command{
Use: metadata.MetadataSubcommandName,
Use: manager.MetadataSubcommandName,
Hidden: true,
// Suppress the global/parent PersistentPreRunE, which
// needlessly initializes the client and tries to
@ -225,8 +200,8 @@ func newMetadataSubcommand(plugin *cobra.Command, meta metadata.Metadata) *cobra
// RunningStandalone tells a CLI plugin it is run standalone by direct execution
func RunningStandalone() bool {
if os.Getenv(metadata.ReexecEnvvar) != "" {
if os.Getenv(manager.ReexecEnvvar) != "" {
return false
}
return len(os.Args) < 2 || os.Args[1] != metadata.MetadataSubcommandName
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName
}

View File

@ -1,28 +0,0 @@
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

@ -178,39 +178,24 @@ func TestConnectAndWait(t *testing.T) {
// TODO: this test cannot be executed with `t.Parallel()`, due to
// relying on goroutine numbers to ensure correct behaviour
t.Run("connect goroutine exits after EOF", func(t *testing.T) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
srv, err := NewPluginServer(nil)
assert.NilError(t, err, "failed to setup server")
defer srv.Close()
t.Setenv(EnvKey, srv.Addr().String())
runtime.Gosched()
numGoroutines := runtime.NumGoroutine()
ConnectAndWait(func() {})
runtime.Gosched()
poll.WaitOn(t, func(t poll.LogT) poll.Result {
// +1 goroutine for the poll.WaitOn
// +1 goroutine for the connect goroutine
if runtime.NumGoroutine() < numGoroutines+1+1 {
return poll.Continue("waiting for connect goroutine to spawn")
}
return poll.Success()
}, poll.WithDelay(1*time.Millisecond), poll.WithTimeout(500*time.Millisecond))
assert.Equal(t, runtime.NumGoroutine(), numGoroutines+1)
srv.Close()
runtime.Gosched()
poll.WaitOn(t, func(t poll.LogT) poll.Result {
// +1 goroutine for the poll.WaitOn
if runtime.NumGoroutine() > numGoroutines+1 {
return poll.Continue("waiting for connect goroutine to exit")
}
return poll.Success()
}, poll.WithDelay(1*time.Millisecond), poll.WithTimeout(500*time.Millisecond))
}, poll.WithDelay(1*time.Millisecond), poll.WithTimeout(10*time.Millisecond))
})
}

View File

@ -3,15 +3,19 @@ package cli
import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"github.com/docker/cli/cli-plugins/metadata"
pluginmanager "github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli/command"
cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/docker/pkg/homedir"
"github.com/docker/docker/registry"
"github.com/fvbommel/sortorder"
"github.com/moby/term"
"github.com/morikuni/aec"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@ -58,6 +62,13 @@ func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *c
"docs.code-delimiter": `"`, // https://github.com/docker/cli-docs-tool/blob/77abede22166eaea4af7335096bdcedd043f5b19/annotation/annotation.go#L20-L22
}
// Configure registry.CertsDir() when running in rootless-mode
if os.Getenv("ROOTLESSKIT_STATE_DIR") != "" {
if configHome, err := homedir.GetConfigHome(); err == nil {
registry.SetCertsDir(filepath.Join(configHome, "docker/certs.d"))
}
}
return opts, helpCommand
}
@ -81,8 +92,12 @@ func FlagErrorFunc(cmd *cobra.Command, err error) error {
return nil
}
usage := ""
if cmd.HasSubCommands() {
usage = "\n\n" + cmd.UsageString()
}
return StatusError{
Status: fmt.Sprintf("%s\n\nUsage: %s\n\nRun '%s --help' for more information", err, cmd.UseLine(), cmd.CommandPath()),
Status: fmt.Sprintf("%s\nSee '%s --help'.%s", err, cmd.CommandPath(), usage),
StatusCode: 125,
}
}
@ -166,6 +181,35 @@ 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",
@ -174,7 +218,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 fmt.Errorf("unknown help topic: %v", strings.Join(args, " "))
return errors.Errorf("unknown help topic: %v", strings.Join(args, " "))
}
helpFunc := cmd.HelpFunc()
helpFunc(cmd, args)
@ -212,7 +256,7 @@ func hasAdditionalHelp(cmd *cobra.Command) bool {
}
func isPlugin(cmd *cobra.Command) bool {
return cmd.Annotations[metadata.CommandAnnotationPlugin] == "true"
return pluginmanager.IsPluginCommand(cmd)
}
func hasAliases(cmd *cobra.Command) bool {
@ -250,12 +294,11 @@ func commandAliases(cmd *cobra.Command) string {
if cmd.HasParent() {
parentPath = cmd.Parent().CommandPath() + " "
}
var aliases strings.Builder
aliases.WriteString(cmd.CommandPath())
aliases := cmd.CommandPath()
for _, alias := range cmd.Aliases {
aliases.WriteString(", " + parentPath + alias)
aliases += ", " + parentPath + alias
}
return aliases.String()
return aliases
}
func topCommands(cmd *cobra.Command) []*cobra.Command {
@ -298,10 +341,8 @@ func operationSubCommands(cmd *cobra.Command) []*cobra.Command {
return cmds
}
const defaultTermWidth = 80
func wrappedFlagUsages(cmd *cobra.Command) string {
width := defaultTermWidth
width := 80
if ws, err := term.GetWinsize(0); err == nil {
width = int(ws.Width)
}
@ -317,9 +358,9 @@ func decoratedName(cmd *cobra.Command) string {
}
func vendorAndVersion(cmd *cobra.Command) string {
if vendor, ok := cmd.Annotations[metadata.CommandAnnotationPluginVendor]; ok && isPlugin(cmd) {
if vendor, ok := cmd.Annotations[pluginmanager.CommandAnnotationPluginVendor]; ok && isPlugin(cmd) {
version := ""
if v, ok := cmd.Annotations[metadata.CommandAnnotationPluginVersion]; ok && v != "" {
if v, ok := cmd.Annotations[pluginmanager.CommandAnnotationPluginVersion]; ok && v != "" {
version = ", " + v
}
return fmt.Sprintf("(%s%s)", vendor, version)
@ -351,10 +392,13 @@ func orchestratorSubCommands(cmd *cobra.Command) []*cobra.Command {
func allManagementSubCommands(cmd *cobra.Command) []*cobra.Command {
cmds := []*cobra.Command{}
for _, sub := range cmd.Commands() {
if invalidPluginReason(sub) != "" {
if isPlugin(sub) {
if invalidPluginReason(sub) == "" {
cmds = append(cmds, sub)
}
continue
}
if sub.IsAvailableCommand() && (isPlugin(sub) || sub.HasSubCommands()) {
if sub.IsAvailableCommand() && sub.HasSubCommands() {
cmds = append(cmds, sub)
}
}
@ -375,7 +419,7 @@ func invalidPlugins(cmd *cobra.Command) []*cobra.Command {
}
func invalidPluginReason(cmd *cobra.Command) string {
return cmd.Annotations[metadata.CommandAnnotationPluginInvalid]
return cmd.Annotations[pluginmanager.CommandAnnotationPluginInvalid]
}
const usageTemplate = `Usage:
@ -478,4 +522,4 @@ Run '{{.CommandPath}} COMMAND --help' for more information on a command.
`
const helpTemplate = `
{{- if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`

View File

@ -3,13 +3,35 @@ package cli
import (
"testing"
"github.com/docker/cli/cli-plugins/metadata"
pluginmanager "github.com/docker/cli/cli-plugins/manager"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
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"}), "")
@ -27,9 +49,9 @@ func TestVendorAndVersion(t *testing.T) {
cmd := &cobra.Command{
Use: "test",
Annotations: map[string]string{
metadata.CommandAnnotationPlugin: "true",
metadata.CommandAnnotationPluginVendor: tc.vendor,
metadata.CommandAnnotationPluginVersion: tc.version,
pluginmanager.CommandAnnotationPlugin: "true",
pluginmanager.CommandAnnotationPluginVendor: tc.vendor,
pluginmanager.CommandAnnotationPluginVersion: tc.version,
},
}
assert.Equal(t, vendorAndVersion(cmd), tc.expected)
@ -47,8 +69,8 @@ func TestInvalidPlugin(t *testing.T) {
assert.Assert(t, is.Len(invalidPlugins(root), 0))
sub1.Annotations = map[string]string{
metadata.CommandAnnotationPlugin: "true",
metadata.CommandAnnotationPluginInvalid: "foo",
pluginmanager.CommandAnnotationPlugin: "true",
pluginmanager.CommandAnnotationPluginInvalid: "foo",
}
root.AddCommand(sub1, sub2)
sub1.AddCommand(sub1sub1, sub1sub2)
@ -56,33 +78,6 @@ 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"}}
@ -105,6 +100,6 @@ func TestDecoratedName(t *testing.T) {
topLevelCommand := &cobra.Command{Use: "pluginTopLevelCommand"}
root.AddCommand(topLevelCommand)
assert.Equal(t, decoratedName(topLevelCommand), "pluginTopLevelCommand ")
topLevelCommand.Annotations = map[string]string{metadata.CommandAnnotationPlugin: "true"}
topLevelCommand.Annotations = map[string]string{pluginmanager.CommandAnnotationPlugin: "true"}
assert.Equal(t, decoratedName(topLevelCommand), "pluginTopLevelCommand*")
}

View File

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

View File

@ -6,52 +6,20 @@ 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"
)
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 {
// 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),
// 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),
NewPruneCommand(dockerCli),
image.NewBuildCommand(dockerCli),
)
return cmd
}
// 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 {
return &cobra.Command{
Use: "bake [OPTIONS] [TARGET...]",
Short: "Build from a file",
RunE: command.ShowHelp(dockerCLI.Err()),
Annotations: map[string]string{
// We want to show this command in the "top" category in --help
// output, and not to be grouped under "management commands".
"category-top": "5",
"aliases": "docker buildx bake",
"version": "1.31",
},
DisableFlagsInUseLine: true,
}
}

View File

@ -8,30 +8,23 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/system/pruner"
"github.com/docker/cli/internal/prompt"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
"github.com/docker/go-units"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/errdefs"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
)
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)
}
}
type pruneOptions struct {
force bool
all bool
filter opts.FilterOpt
reservedSpace opts.MemBytes
force bool
all bool
filter opts.FilterOpt
keepStorage opts.MemBytes
}
// newPruneCommand returns a new cobra prune command for images
func newPruneCommand(dockerCLI command.Cli) *cobra.Command {
// NewPruneCommand returns a new cobra prune command for images
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
options := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
@ -39,26 +32,25 @@ 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: cobra.NoFileCompletions,
DisableFlagsInUseLine: true,
Annotations: map[string]string{"version": "1.39"},
ValidArgsFunction: completion.NoComplete,
}
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.reservedSpace, "keep-storage", "Amount of disk space to keep for cache")
flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")
return cmd
}
@ -69,31 +61,32 @@ const (
)
func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
pruneFilters := options.filter.Value()
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
warning := normalWarning
if options.all {
warning = allCacheWarning
}
if !options.force {
r, err := prompt.Confirm(ctx, dockerCli.In(), dockerCli.Out(), warning)
r, err := command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), warning)
if err != nil {
return 0, "", err
}
if !r {
return 0, "", cancelledErr{errors.New("builder prune has been cancelled")}
return 0, "", errdefs.Cancelled(errors.New("builder prune has been cancelled"))
}
}
resp, err := dockerCli.Client().BuildCachePrune(ctx, client.BuildCachePruneOptions{
All: options.all,
ReservedSpace: options.reservedSpace.Value(),
Filters: pruneFilters,
report, err := dockerCli.Client().BuildCachePrune(ctx, types.BuildCachePruneOptions{
All: options.all,
KeepStorage: options.keepStorage.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")
@ -107,26 +100,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
return report.SpaceReclaimed, output, nil
}
type cancelledErr struct{ error }
func (cancelledErr) Cancelled() {}
// 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,
})
// 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})
}

View File

@ -7,7 +7,7 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types"
)
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 client.BuildCachePruneOptions) (client.BuildCachePruneResult, error) {
return client.BuildCachePruneResult{}, errors.New("fakeClient builderPruneFunc should not be called")
builderPruneFunc: func(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) {
return nil, errors.New("fakeClient builderPruneFunc should not be called")
},
})
cmd := newPruneCommand(cli)
cmd := NewPruneCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
test.TerminatePrompt(ctx, t, cmd, cli)

View File

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

View File

@ -3,32 +3,26 @@ package checkpoint
import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/internal/commands"
"github.com/spf13/cobra"
)
func init() {
commands.Register(newCheckpointCommand)
}
// newCheckpointCommand returns the `checkpoint` subcommand (only in experimental)
func newCheckpointCommand(dockerCLI command.Cli) *cobra.Command {
// 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,7 +6,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/moby/moby/client"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/checkpoint"
"github.com/spf13/cobra"
)
@ -17,7 +18,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{
@ -27,10 +28,9 @@ 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: cobra.NoFileCompletions,
DisableFlagsInUseLine: true,
ValidArgsFunction: completion.NoComplete,
}
flags := cmd.Flags()
@ -40,8 +40,8 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
return cmd
}
func runCreate(ctx context.Context, dockerCLI command.Cli, opts createOptions) error {
_, err := dockerCLI.Client().CheckpointCreate(ctx, opts.container, client.CheckpointCreateOptions{
func runCreate(ctx context.Context, dockerCli command.Cli, opts createOptions) error {
err := dockerCli.Client().CheckpointCreate(ctx, opts.container, checkpoint.CreateOptions{
CheckpointID: opts.checkpoint,
CheckpointDir: opts.checkpointDir,
Exit: !opts.leaveRunning,
@ -50,6 +50,6 @@ func runCreate(ctx context.Context, dockerCLI command.Cli, opts createOptions) e
return err
}
_, _ = fmt.Fprintln(dockerCLI.Out(), opts.checkpoint)
fmt.Fprintf(dockerCli.Out(), "%s\n", opts.checkpoint)
return nil
}

View File

@ -1,14 +1,13 @@
package checkpoint
import (
"errors"
"io"
"strconv"
"strings"
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/checkpoint"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@ -16,21 +15,21 @@ import (
func TestCheckpointCreateErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointCreateFunc func(container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error)
checkpointCreateFunc func(container string, options checkpoint.CreateOptions) error
expectedError string
}{
{
args: []string{"too-few-arguments"},
expectedError: "requires 2 arguments",
expectedError: "requires exactly 2 arguments",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires 2 arguments",
expectedError: "requires exactly 2 arguments",
},
{
args: []string{"foo", "bar"},
checkpointCreateFunc: func(container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error) {
return client.CheckpointCreateResult{}, errors.New("error creating checkpoint for container foo")
checkpointCreateFunc: func(container string, options checkpoint.CreateOptions) error {
return errors.Errorf("error creating checkpoint for container foo")
},
expectedError: "error creating checkpoint for container foo",
},
@ -49,39 +48,26 @@ func TestCheckpointCreateErrors(t *testing.T) {
}
func TestCheckpointCreateWithOptions(t *testing.T) {
const (
containerName = "container-foo"
checkpointName = "checkpoint-bar"
checkpointDir = "/dir/foo"
)
for _, tc := range []bool{true, false} {
leaveRunning := strconv.FormatBool(tc)
t.Run("leave-running="+leaveRunning, func(t *testing.T) {
var actualContainerName string
var actualOptions client.CheckpointCreateOptions
cli := test.NewFakeCli(&fakeClient{
checkpointCreateFunc: func(container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error) {
actualContainerName = container
actualOptions = options
return client.CheckpointCreateResult{}, nil
},
})
cmd := newCreateCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs([]string{containerName, checkpointName})
assert.Check(t, cmd.Flags().Set("leave-running", leaveRunning))
assert.Check(t, cmd.Flags().Set("checkpoint-dir", checkpointDir))
assert.NilError(t, cmd.Execute())
assert.Check(t, is.Equal(actualContainerName, containerName))
expected := client.CheckpointCreateOptions{
CheckpointID: checkpointName,
CheckpointDir: checkpointDir,
Exit: !tc,
}
assert.Check(t, is.Equal(actualOptions, expected))
assert.Check(t, is.Equal(strings.TrimSpace(cli.OutBuffer().String()), checkpointName))
})
}
var containerID, checkpointID, checkpointDir string
var exit bool
cli := test.NewFakeCli(&fakeClient{
checkpointCreateFunc: func(container string, options checkpoint.CreateOptions) error {
containerID = container
checkpointID = options.CheckpointID
checkpointDir = options.CheckpointDir
exit = options.Exit
return nil
},
})
cmd := newCreateCommand(cli)
cp := "checkpoint-bar"
cmd.SetArgs([]string{"container-foo", cp})
cmd.Flags().Set("leave-running", "true")
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(cp, checkpointID))
assert.Check(t, is.Equal("/dir/foo", checkpointDir))
assert.Check(t, is.Equal(false, exit))
assert.Check(t, is.Equal(cp, strings.TrimSpace(cli.OutBuffer().String())))
}

View File

@ -2,7 +2,7 @@ package checkpoint
import (
"github.com/docker/cli/cli/command/formatter"
"github.com/moby/moby/api/types/checkpoint"
"github.com/docker/docker/api/types/checkpoint"
)
const (
@ -10,31 +10,25 @@ const (
checkpointNameHeader = "CHECKPOINT NAME"
)
// newFormat returns a format for use with a checkpointContext.
func newFormat(source string) formatter.Format {
// NewFormat returns a format for use with a checkpoint Context
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(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 {
// 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 {
for _, cp := range checkpoints {
if err := format(&checkpointContext{c: cp}); err != nil {
return err
}
}
return nil
})
}
return ctx.Write(newCheckpointContext(), render)
}
type checkpointContext struct {
@ -42,6 +36,14 @@ 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/moby/moby/api/types/checkpoint"
"github.com/docker/docker/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/moby/moby/client"
"github.com/docker/docker/api/types/checkpoint"
"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,10 +24,9 @@ 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),
DisableFlagsInUseLine: true,
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
}
flags := cmd.Flags()
@ -36,8 +35,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, client.CheckpointListOptions{
func runList(ctx context.Context, dockerCli command.Cli, container string, opts listOptions) error {
checkpoints, err := dockerCli.Client().CheckpointList(ctx, container, checkpoint.ListOptions{
CheckpointDir: opts.checkpointDir,
})
if err != nil {
@ -45,8 +44,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.Items)
return FormatWrite(cpCtx, checkpoints)
}

View File

@ -1,13 +1,12 @@
package checkpoint
import (
"errors"
"io"
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/api/types/checkpoint"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/checkpoint"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
@ -16,21 +15,21 @@ import (
func TestCheckpointListErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointListFunc func(container string, options client.CheckpointListOptions) (client.CheckpointListResult, error)
checkpointListFunc func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error)
expectedError string
}{
{
args: []string{},
expectedError: "requires 1 argument",
expectedError: "requires exactly 1 argument",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires 1 argument",
expectedError: "requires exactly 1 argument",
},
{
args: []string{"foo"},
checkpointListFunc: func(container string, options client.CheckpointListOptions) (client.CheckpointListResult, error) {
return client.CheckpointListResult{}, errors.New("error getting checkpoints for container foo")
checkpointListFunc: func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) {
return []checkpoint.Summary{}, errors.Errorf("error getting checkpoints for container foo")
},
expectedError: "error getting checkpoints for container foo",
},
@ -51,19 +50,17 @@ func TestCheckpointListErrors(t *testing.T) {
func TestCheckpointListWithOptions(t *testing.T) {
var containerID, checkpointDir string
cli := test.NewFakeCli(&fakeClient{
checkpointListFunc: func(container string, options client.CheckpointListOptions) (client.CheckpointListResult, error) {
checkpointListFunc: func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) {
containerID = container
checkpointDir = options.CheckpointDir
return client.CheckpointListResult{
Items: []checkpoint.Summary{
{Name: "checkpoint-foo"},
},
return []checkpoint.Summary{
{Name: "checkpoint-foo"},
}, nil
},
})
cmd := newListCommand(cli)
cmd.SetArgs([]string{"container-foo"})
assert.Check(t, cmd.Flags().Set("checkpoint-dir", "/dir/foo"))
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,9 +1,11 @@
package checkpoint
import (
"context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/checkpoint"
"github.com/spf13/cobra"
)
@ -11,7 +13,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{
@ -20,14 +22,8 @@ func newRemoveCommand(dockerCLI command.Cli) *cobra.Command {
Short: "Remove a checkpoint",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
containerID, checkpointID := args[0], args[1]
_, err := dockerCLI.Client().CheckpointRemove(cmd.Context(), containerID, client.CheckpointRemoveOptions{
CheckpointID: checkpointID,
CheckpointDir: opts.checkpointDir,
})
return err
return runRemove(cmd.Context(), dockerCli, args[0], args[1], opts)
},
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -35,3 +31,10 @@ 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

@ -1,12 +1,12 @@
package checkpoint
import (
"errors"
"io"
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/checkpoint"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@ -14,21 +14,21 @@ import (
func TestCheckpointRemoveErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointDeleteFunc func(container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error)
checkpointDeleteFunc func(container string, options checkpoint.DeleteOptions) error
expectedError string
}{
{
args: []string{"too-few-arguments"},
expectedError: "requires 2 arguments",
expectedError: "requires exactly 2 arguments",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires 2 arguments",
expectedError: "requires exactly 2 arguments",
},
{
args: []string{"foo", "bar"},
checkpointDeleteFunc: func(container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error) {
return client.CheckpointRemoveResult{}, errors.New("error deleting checkpoint")
checkpointDeleteFunc: func(container string, options checkpoint.DeleteOptions) error {
return errors.Errorf("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 client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error) {
checkpointDeleteFunc: func(container string, options checkpoint.DeleteOptions) error {
containerID = container
checkpointID = options.CheckpointID
checkpointDir = options.CheckpointDir
return client.CheckpointRemoveResult{}, nil
return nil
},
})
cmd := newRemoveCommand(cli)
cmd.SetArgs([]string{"container-foo", "checkpoint-bar"})
assert.Check(t, cmd.Flags().Set("checkpoint-dir", "/dir/foo"))
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,14 +1,14 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.22
package command
import (
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strconv"
"sync"
@ -21,12 +21,21 @@ import (
"github.com/docker/cli/cli/context/store"
"github.com/docker/cli/cli/debug"
cliflags "github.com/docker/cli/cli/flags"
manifeststore "github.com/docker/cli/cli/manifest/store"
registryclient "github.com/docker/cli/cli/registry/client"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/cli/version"
dopts "github.com/docker/cli/opts"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/client"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/go-connections/tlsconfig"
"github.com/pkg/errors"
"github.com/spf13/cobra"
notaryclient "github.com/theupdateframework/notary/client"
)
const defaultInitTimeout = 2 * time.Second
@ -43,9 +52,15 @@ type Cli interface {
Client() client.APIClient
Streams
SetIn(in *streams.In)
config.Provider
Apply(ops ...CLIOption) error
ConfigFile() *configfile.ConfigFile
ServerInfo() ServerInfo
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
DefaultVersion() string
CurrentVersion() string
ManifestStore() manifeststore.Store
RegistryClient(bool) registryclient.RegistryClient
ContentTrustEnabled() bool
BuildKitEnabled() (bool, error)
ContextStore() store.Store
CurrentContext() string
@ -54,9 +69,7 @@ type Cli interface {
}
// DockerCli is an instance the docker command line client.
// Instances of the client should be created using the [NewDockerCli]
// constructor to make sure they are properly initialized with defaults
// set.
// Instances of the client can be returned from NewDockerCli.
type DockerCli struct {
configFile *configfile.ConfigFile
options *cliflags.ClientOptions
@ -65,14 +78,14 @@ type DockerCli struct {
err *streams.Out
client client.APIClient
serverInfo ServerInfo
contentTrust bool
contextStore store.Store
currentContext string
init sync.Once
initErr error
dockerEndpoint docker.Endpoint
contextStoreConfig *store.Config
contextStoreConfig store.Config
initTimeout time.Duration
userAgent string
res telemetryResource
// baseCtx is the base context used for internal operations. In the future
@ -83,12 +96,17 @@ type DockerCli struct {
enableGlobalMeter, enableGlobalTracer bool
}
// DefaultVersion returns api.defaultVersion.
func (cli *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 client.MaxAPIVersion
return api.DefaultVersion
}
return cli.client.ClientVersion()
}
@ -96,7 +114,7 @@ func (cli *DockerCli) CurrentVersion() string {
// Client returns the APIClient
func (cli *DockerCli) Client() client.APIClient {
if err := cli.initialize(); err != nil {
_, _ = fmt.Fprintln(cli.Err(), "Failed to initialize:", err)
_, _ = fmt.Fprintf(cli.Err(), "Failed to initialize: %s\n", err)
os.Exit(1)
}
return cli.client
@ -147,13 +165,19 @@ 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, fmt.Errorf("DOCKER_BUILDKIT environment variable expects boolean value: %w", err)
return false, errors.Wrap(err, "DOCKER_BUILDKIT environment variable expects boolean value")
}
return enabled, nil
}
@ -164,7 +188,7 @@ func (cli *DockerCli) BuildKitEnabled() (bool, error) {
}
si := cli.ServerInfo()
if si.BuildkitVersion == build.BuilderBuildKit {
if si.BuildkitVersion == types.BuilderBuildKit {
// The daemon advertised BuildKit as the preferred builder; this may
// be either a Linux daemon or a Windows daemon with experimental
// BuildKit support enabled.
@ -178,16 +202,16 @@ func (cli *DockerCli) BuildKitEnabled() (bool, error) {
// HooksEnabled returns whether plugin hooks are enabled.
func (cli *DockerCli) HooksEnabled() bool {
// use DOCKER_CLI_HOOKS env var value if set and not empty
if v := os.Getenv("DOCKER_CLI_HOOKS"); v != "" {
// legacy support DOCKER_CLI_HINTS env var
if v := os.Getenv("DOCKER_CLI_HINTS"); v != "" {
enabled, err := strconv.ParseBool(v)
if err != nil {
return false
}
return enabled
}
// legacy support DOCKER_CLI_HINTS env var
if v := os.Getenv("DOCKER_CLI_HINTS"); v != "" {
// use DOCKER_CLI_HOOKS env var value if set and not empty
if v := os.Getenv("DOCKER_CLI_HOOKS"); v != "" {
enabled, err := strconv.ParseBool(v)
if err != nil {
return false
@ -206,6 +230,30 @@ func (cli *DockerCli) HooksEnabled() bool {
return false
}
// ManifestStore returns a store for local manifests
func (cli *DockerCli) ManifestStore() manifeststore.Store {
// TODO: support override default location from config file
return manifeststore.NewStore(filepath.Join(config.Dir(), "manifests"))
}
// RegistryClient returns a client for communicating with a Docker distribution
// registry
func (cli *DockerCli) RegistryClient(allowInsecure bool) registryclient.RegistryClient {
resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig {
return ResolveAuthConfig(cli.ConfigFile(), index)
}
return registryclient.NewRegistryClient(resolver, UserAgent(), allowInsecure)
}
// WithInitializeClient is passed to DockerCli.Initialize by callers who wish to set a particular API Client for use by the CLI.
func WithInitializeClient(makeClient func(dockerCli *DockerCli) (client.APIClient, error)) CLIOption {
return func(dockerCli *DockerCli) error {
var err error
dockerCli.client, err = makeClient(dockerCli)
return err
}
}
// Initialize the dockerCli runs initialization that must happen after command
// line flags are parsed.
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) error {
@ -224,36 +272,16 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
debug.Enable()
}
if opts.Context != "" && len(opts.Hosts) > 0 {
return errors.New("conflicting options: cannot specify both --host and --context")
}
if cli.contextStoreConfig == nil {
// This path can be hit when calling Initialize on a DockerCli that's
// not constructed through [NewDockerCli]. Using the default context
// store without a config set will result in Endpoints from contexts
// not being type-mapped correctly, and used as a generic "map[string]any",
// instead of a [docker.EndpointMeta].
//
// When looking up the API endpoint (using [EndpointFromContext]), no
// endpoint will be found, and a default, empty endpoint will be used
// instead which in its turn, causes newAPIClientFromEndpoint to
// be initialized with the default config instead of settings for
// the current context (which may mean; connecting with the wrong
// endpoint and/or TLS Config to be missing).
//
// [EndpointFromContext]: https://github.com/docker/cli/blob/33494921b80fd0b5a06acc3a34fa288de4bb2e6b/cli/context/docker/load.go#L139-L149
if err := WithDefaultContextStoreConfig()(cli); err != nil {
return err
}
return errors.New("conflicting options: either specify --host or --context, not both")
}
cli.options = opts
cli.configFile = config.LoadDefaultConfigFile(cli.err)
cli.currentContext = resolveContextName(cli.options, cli.configFile)
cli.contextStore = &ContextStoreWithDefault{
Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig),
Store: store.New(config.ContextStoreDir(), cli.contextStoreConfig),
Resolver: func() (*DefaultContext, error) {
return resolveDefaultContext(cli.options, *cli.contextStoreConfig)
return ResolveDefaultContext(cli.options, cli.contextStoreConfig)
},
}
@ -264,18 +292,6 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
if cli.enableGlobalTracer {
cli.createGlobalTracerProvider(cli.baseCtx)
}
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
}
@ -283,24 +299,24 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
// NewAPIClientFromFlags creates a new APIClient from command line flags
func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
if opts.Context != "" && len(opts.Hosts) > 0 {
return nil, errors.New("conflicting options: cannot specify both --host and --context")
return nil, errors.New("conflicting options: either specify --host or --context, not both")
}
storeConfig := DefaultContextStoreConfig()
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, fmt.Errorf("unable to resolve docker endpoint: %w", err)
return nil, errors.Wrap(err, "unable to resolve docker endpoint")
}
return newAPIClientFromEndpoint(endpoint, configFile, client.WithUserAgent(UserAgent()))
return newAPIClientFromEndpoint(endpoint, configFile)
}
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile, extraOpts ...client.Opt) (client.APIClient, error) {
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile) (client.APIClient, error) {
opts, err := ep.ClientOpts()
if err != nil {
return nil, err
@ -308,15 +324,8 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF
if len(configFile.HTTPHeaders) > 0 {
opts = append(opts, client.WithHTTPHeaders(configFile.HTTPHeaders))
}
withCustomHeaders, err := withCustomHeadersFromEnv()
if err != nil {
return nil, err
}
if withCustomHeaders != nil {
opts = append(opts, withCustomHeaders)
}
opts = append(opts, extraOpts...)
return client.New(opts...)
opts = append(opts, withCustomHeadersFromEnv(), client.WithUserAgent(UserAgent()))
return client.NewClientWithOpts(opts...)
}
func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint, error) {
@ -336,10 +345,7 @@ func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint,
// Resolve the Docker endpoint for the default context (based on config, env vars and CLI flags)
func resolveDefaultDockerEndpoint(opts *cliflags.ClientOptions) (docker.Endpoint, error) {
// defaultToTLS determines whether we should use a TLS host as default
// if nothing was configured by the user.
defaultToTLS := opts.TLSOptions != nil
host, err := getServerHost(opts.Hosts, defaultToTLS)
host, err := getServerHost(opts.Hosts, opts.TLSOptions)
if err != nil {
return docker.Endpoint{}, err
}
@ -377,21 +383,29 @@ func (cli *DockerCli) initializeFromClient() {
ctx, cancel := context.WithTimeout(cli.baseCtx, cli.getInitTimeout())
defer cancel()
ping, err := cli.client.Ping(ctx, client.PingOptions{
NegotiateAPIVersion: true,
ForceNegotiate: true,
})
ping, err := cli.client.Ping(ctx)
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)
}
// NotaryClient provides a Notary Repository to interact with signed metadata for an image
func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) {
return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...)
}
// ContextStore returns the ContextStore
@ -461,7 +475,7 @@ func (cli *DockerCli) DockerEndpoint() docker.Endpoint {
if err := cli.initialize(); err != nil {
// Note that we're not terminating here, as this function may be used
// in cases where we're able to continue.
_, _ = fmt.Fprintln(cli.Err(), cli.initErr)
_, _ = fmt.Fprintf(cli.Err(), "%v\n", cli.initErr)
}
return cli.dockerEndpoint
}
@ -474,67 +488,15 @@ 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 = fmt.Errorf("unable to resolve docker endpoint: %w", cli.initErr)
cli.initErr = errors.Wrap(cli.initErr, "unable to resolve docker endpoint")
return
}
if cli.client == nil {
ops := []client.Opt{client.WithUserAgent(cli.userAgent)}
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil {
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile); cli.initErr != nil {
return
}
}
@ -546,12 +508,22 @@ 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 {
HasExperimental bool
OSType string
BuildkitVersion build.BuilderVersion
BuildkitVersion types.BuilderVersion
// SwarmStatus provides information about the current swarm status of the
// engine, obtained from the "Swarm" header in the API response.
@ -560,7 +532,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 *client.SwarmStatus
SwarmStatus *swarm.Status
}
// NewDockerCli returns a DockerCli instance with all operators applied on it.
@ -568,33 +540,34 @@ 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()}
for _, op := range ops {
if err := op(cli); err != nil {
return nil, err
}
if err := cli.Apply(ops...); err != nil {
return nil, err
}
return cli, nil
}
func getServerHost(hosts []string, defaultToTLS bool) (string, error) {
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error) {
var host string
switch len(hosts) {
case 0:
return dopts.ParseHost(defaultToTLS, os.Getenv(client.EnvOverrideHost))
host = os.Getenv(client.EnvOverrideHost)
case 1:
return dopts.ParseHost(defaultToTLS, hosts[0])
host = hosts[0]
default:
return "", errors.New("specify only one -H")
return "", errors.New("Specify only one -H")
}
return dopts.ParseHost(tlsOptions != nil, host)
}
// UserAgent returns the default user agent string used for making API requests.
// UserAgent returns the user agent string used for making API requests
func UserAgent() string {
return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")"
}

View File

@ -3,16 +3,17 @@ package command
import (
"context"
"encoding/csv"
"errors"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"github.com/docker/cli/cli/streams"
"github.com/moby/moby/client"
"github.com/docker/docker/client"
"github.com/docker/docker/errdefs"
"github.com/moby/term"
"github.com/pkg/errors"
)
// CLIOption is a functional argument to apply options to a [DockerCli]. These
@ -75,11 +76,32 @@ 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 {
cfg := DefaultContextStoreConfig()
cli.contextStoreConfig = &cfg
cli.contextStoreConfig = DefaultContextStoreConfig()
return nil
}
}
@ -92,18 +114,6 @@ func WithAPIClient(c client.APIClient) CLIOption {
}
}
// WithInitializeClient is passed to [DockerCli.Initialize] to initialize
// an API Client for use by the CLI.
func WithInitializeClient(makeClient func(*DockerCli) (client.APIClient, error)) CLIOption {
return func(cli *DockerCli) error {
c, err := makeClient(cli)
if err != nil {
return err
}
return WithAPIClient(c)(cli)
}
}
// envOverrideHTTPHeaders is the name of the environment-variable that can be
// used to set custom HTTP headers to be sent by the client. This environment
// variable is the equivalent to the HttpHeaders field in the configuration
@ -158,70 +168,52 @@ 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, 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,
))
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 errdefs.InvalidParameter(errors.Errorf("failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs", envOverrideHTTPHeaders))
}
if len(fields) == 0 {
return nil
}
// 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, "=")
// Only strip whitespace in keys; preserve whitespace in values.
k = strings.TrimSpace(k)
if k == "" {
return errdefs.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))
}
// 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 errdefs.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
}
env[http.CanonicalHeaderKey(k)] = v
}
if len(env) == 0 {
// We should probably not hit this case, as we don't skip values
// (only return errors), but we don't want to discard existing
// headers with an empty set.
return nil, nil
}
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
return client.WithHTTPHeaders(env), nil
}
// 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
}
cli.userAgent = userAgent
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)
}
}

View File

@ -0,0 +1,28 @@
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

@ -3,7 +3,6 @@ package command
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net"
@ -18,10 +17,14 @@ 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/moby/moby/client"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
"gotest.tools/v3/fs"
)
func TestNewAPIClientFromFlags(t *testing.T) {
@ -33,7 +36,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(), client.MaxAPIVersion)
assert.Equal(t, apiClient.ClientVersion(), api.DefaultVersion)
}
func TestNewAPIClientFromFlagsForDefaultSchema(t *testing.T) {
@ -46,7 +49,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(), client.MaxAPIVersion)
assert.Equal(t, apiClient.ClientVersion(), api.DefaultVersion)
}
func TestNewAPIClientFromFlagsWithCustomHeaders(t *testing.T) {
@ -70,7 +73,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(), client.MaxAPIVersion)
assert.Equal(t, apiClient.ClientVersion(), api.DefaultVersion)
// 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"})
@ -79,7 +82,7 @@ func TestNewAPIClientFromFlagsWithCustomHeaders(t *testing.T) {
"My-Header": "Custom-Value",
"User-Agent": UserAgent(),
}
_, err = apiClient.Ping(context.TODO(), client.PingOptions{})
_, err = apiClient.Ping(context.Background())
assert.NilError(t, err)
assert.DeepEqual(t, received, expectedHeaders)
}
@ -105,7 +108,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(), client.MaxAPIVersion)
assert.Equal(t, apiClient.ClientVersion(), api.DefaultVersion)
expectedHeaders := http.Header{
"One": []string{"one-value"},
@ -114,14 +117,13 @@ func TestNewAPIClientFromFlagsWithCustomHeadersFromEnv(t *testing.T) {
"Four": []string{"four-value-override"},
"User-Agent": []string{UserAgent()},
}
_, err = apiClient.Ping(context.TODO(), client.PingOptions{})
_, err = apiClient.Ping(context.Background())
assert.NilError(t, err)
assert.DeepEqual(t, received, expectedHeaders)
}
func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
const customVersion = "v3.3"
const expectedVersion = "3.3"
customVersion := "v3.3.3"
t.Setenv("DOCKER_API_VERSION", customVersion)
t.Setenv("DOCKER_HOST", ":2375")
@ -129,78 +131,75 @@ func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
configFile := &configfile.ConfigFile{}
apiclient, err := NewAPIClientFromFlags(opts, configFile)
assert.NilError(t, err)
assert.Equal(t, apiclient.ClientVersion(), expectedVersion)
assert.Equal(t, apiclient.ClientVersion(), customVersion)
}
type fakeClient struct {
client.Client
pingFunc func() (client.PingResult, error)
pingFunc func() (types.Ping, error)
version string
negotiated bool
}
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) Ping(_ context.Context) (types.Ping, error) {
return c.pingFunc()
}
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() (client.PingResult, error)
pingFunc func() (types.Ping, error)
expectedServer ServerInfo
negotiated bool
}{
{
doc: "successful ping",
pingFunc: func() (client.PingResult, error) {
return client.PingResult{Experimental: true, OSType: "linux", APIVersion: "v1.44"}, nil
pingFunc: func() (types.Ping, error) {
return types.Ping{Experimental: true, OSType: "linux", APIVersion: "v1.30"}, nil
},
expectedServer: ServerInfo{HasExperimental: true, OSType: "linux"},
negotiated: true,
},
{
doc: "failed ping, no API version",
pingFunc: func() (client.PingResult, error) {
return client.PingResult{}, errors.New("failed")
pingFunc: func() (types.Ping, error) {
return types.Ping{}, errors.New("failed")
},
expectedServer: ServerInfo{HasExperimental: true},
},
{
doc: "failed ping, with API version",
pingFunc: func() (client.PingResult, error) {
return client.PingResult{APIVersion: "v1.44"}, errors.New("failed")
pingFunc: func() (types.Ping, error) {
return types.Ping{APIVersion: "v1.33"}, errors.New("failed")
},
expectedServer: ServerInfo{HasExperimental: true},
negotiated: true,
},
}
for _, tc := range testcases {
t.Run(tc.doc, func(t *testing.T) {
apiClient := &fakeClient{
pingFunc: tc.pingFunc,
for _, testcase := range testcases {
testcase := testcase
t.Run(testcase.doc, func(t *testing.T) {
apiclient := &fakeClient{
pingFunc: testcase.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.DeepEqual(t, cli.ServerInfo(), testcase.expectedServer)
assert.Equal(t, apiclient.negotiated, testcase.negotiated)
})
}
}
@ -208,13 +207,13 @@ func TestInitializeFromClient(t *testing.T) {
// Makes sure we don't hang forever on the initial connection.
// https://github.com/docker/cli/issues/3652
func TestInitializeFromClientHangs(t *testing.T) {
tmpDir := t.TempDir()
socket := filepath.Join(tmpDir, "my.sock")
dir := t.TempDir()
socket := filepath.Join(dir, "my.sock")
l, err := net.Listen("unix", socket)
assert.NilError(t, err)
receiveReqCh := make(chan bool)
timeoutCtx, cancel := context.WithTimeout(context.TODO(), time.Second)
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// Simulate a server that hangs on connections.
@ -257,33 +256,80 @@ func TestInitializeFromClientHangs(t *testing.T) {
}
}
func TestNewDockerCliAndOperators(t *testing.T) {
outbuf := bytes.NewBuffer(nil)
errbuf := bytes.NewBuffer(nil)
// The CLI no longer disables/hides experimental CLI features, however, we need
// to verify that existing configuration files do not break
func TestExperimentalCLI(t *testing.T) {
defaultVersion := "v1.55"
testcases := []struct {
doc string
configfile string
}{
{
doc: "default",
configfile: `{}`,
},
{
doc: "experimental",
configfile: `{
"experimental": "enabled"
}`,
},
}
for _, testcase := range testcases {
testcase := testcase
t.Run(testcase.doc, func(t *testing.T) {
dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
defer dir.Remove()
apiclient := &fakeClient{
version: defaultVersion,
pingFunc: func() (types.Ping, error) {
return types.Ping{Experimental: true, OSType: "linux", APIVersion: defaultVersion}, nil
},
}
cli := &DockerCli{client: apiclient, err: streams.NewOut(os.Stderr)}
config.SetDir(dir.Path())
err := cli.Initialize(flags.NewClientOptions())
assert.NilError(t, err)
})
}
}
func TestNewDockerCliAndOperators(t *testing.T) {
// Test default operations and also overriding default ones
cli, err := NewDockerCli(
WithInputStream(io.NopCloser(strings.NewReader("some input"))),
WithOutputStream(outbuf),
WithErrorStream(errbuf),
WithContentTrust(true),
)
assert.NilError(t, err)
// Check streams are initialized
assert.Check(t, cli.In() != nil)
assert.Check(t, cli.Out() != nil)
assert.Check(t, cli.Err() != nil)
assert.Equal(t, cli.ContentTrustEnabled(), true)
// Apply can modify a dockerCli after construction
inbuf := bytes.NewBuffer([]byte("input"))
outbuf := bytes.NewBuffer(nil)
errbuf := bytes.NewBuffer(nil)
err = cli.Apply(
WithInputStream(io.NopCloser(inbuf)),
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), "some input")
assert.Equal(t, string(inputStream), "input")
// Check output stream
_, err = fmt.Fprint(cli.Out(), "output")
assert.NilError(t, err)
fmt.Fprintf(cli.Out(), "output")
outputStream, err := io.ReadAll(outbuf)
assert.NilError(t, err)
assert.Equal(t, string(outputStream), "output")
// Check error stream
_, err = fmt.Fprint(cli.Err(), "error")
assert.NilError(t, err)
fmt.Fprintf(cli.Err(), "error")
errStream, err := io.ReadAll(errbuf)
assert.NilError(t, err)
assert.Equal(t, string(errStream), "error")
@ -292,16 +338,14 @@ func TestNewDockerCliAndOperators(t *testing.T) {
func TestInitializeShouldAlwaysCreateTheContextStore(t *testing.T) {
cli, err := NewDockerCli()
assert.NilError(t, err)
apiClient, err := client.New()
assert.NilError(t, err)
assert.NilError(t, cli.Initialize(flags.NewClientOptions(), WithAPIClient(apiClient)))
assert.NilError(t, cli.Initialize(flags.NewClientOptions(), WithInitializeClient(func(cli *DockerCli) (client.APIClient, error) {
return client.NewClientWithOpts()
})))
assert.Check(t, cli.ContextStore() != nil)
}
func TestHooksEnabled(t *testing.T) {
t.Run("disabled by default", func(t *testing.T) {
// Make sure we don't depend on any existing ~/.docker/config.json
config.SetDir(t.TempDir())
cli, err := NewDockerCli()
assert.NilError(t, err)
@ -313,11 +357,12 @@ func TestHooksEnabled(t *testing.T) {
"features": {
"hooks": "true"
}}`
config.SetDir(t.TempDir())
err := os.WriteFile(filepath.Join(config.Dir(), "config.json"), []byte(configFile), 0o600)
assert.NilError(t, err)
dir := fs.NewDir(t, "", fs.WithFile("config.json", configFile))
defer dir.Remove()
cli, err := NewDockerCli()
assert.NilError(t, err)
config.SetDir(dir.Path())
assert.Check(t, cli.HooksEnabled())
})
@ -327,11 +372,12 @@ func TestHooksEnabled(t *testing.T) {
"hooks": "true"
}}`
t.Setenv("DOCKER_CLI_HOOKS", "false")
config.SetDir(t.TempDir())
err := os.WriteFile(filepath.Join(config.Dir(), "config.json"), []byte(configFile), 0o600)
assert.NilError(t, err)
dir := fs.NewDir(t, "", fs.WithFile("config.json", configFile))
defer dir.Remove()
cli, err := NewDockerCli()
assert.NilError(t, err)
config.SetDir(dir.Path())
assert.Check(t, !cli.HooksEnabled())
})
@ -341,54 +387,12 @@ func TestHooksEnabled(t *testing.T) {
"hooks": "true"
}}`
t.Setenv("DOCKER_CLI_HINTS", "false")
config.SetDir(t.TempDir())
err := os.WriteFile(filepath.Join(config.Dir(), "config.json"), []byte(configFile), 0o600)
assert.NilError(t, err)
dir := fs.NewDir(t, "", fs.WithFile("config.json", configFile))
defer dir.Remove()
cli, err := NewDockerCli()
assert.NilError(t, err)
config.SetDir(dir.Path())
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,30 +1,109 @@
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/volume"
"github.com/docker/cli/internal/commands"
"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/spf13/cobra"
)
func AddCommands(cmd *cobra.Command, dockerCLI command.Cli) {
for _, c := range commands.Commands() {
cmd.AddCommand(c(dockerCLI))
}
// 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.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
}
cmdCopy := *cmd
cmdCopy.Hidden = true
cmdCopy.Aliases = []string{}
return &cmdCopy
}

View File

@ -4,14 +4,20 @@ import (
"os"
"strings"
"github.com/distribution/reference"
"github.com/docker/cli/cli/command/formatter"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/spf13/cobra"
)
// APIClientProvider provides a method to get a [client.APIClient], initializing
// ValidArgsFn a function to be used by cobra command as `ValidArgsFunction` to offer command line completion
type ValidArgsFn func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective)
// APIClientProvider provides a method to get an [client.APIClient], initializing
// it if needed.
//
// It's a smaller interface than [command.Cli], and used in situations where an
@ -22,62 +28,26 @@ type APIClientProvider interface {
}
// ImageNames offers completion for images present within the local store
func ImageNames(dockerCLI APIClientProvider, limit int) cobra.CompletionFunc {
func ImageNames(dockerCLI APIClientProvider) ValidArgsFn {
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{})
list, err := dockerCLI.Client().ImageList(cmd.Context(), image.ListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, img := range res.Items {
for _, img := range list {
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 {
func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(types.Container) bool) ValidArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
res, err := dockerCLI.Client().ContainerList(cmd.Context(), client.ContainerListOptions{
list, err := dockerCLI.Client().ContainerList(cmd.Context(), container.ListOptions{
All: all,
})
if err != nil {
@ -87,7 +57,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 res.Items {
for _, ctr := range list {
skip := false
for _, fn := range filters {
if fn != nil && !fn(ctr) {
@ -108,14 +78,14 @@ func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(conta
}
// VolumeNames offers completion for volumes
func VolumeNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
func VolumeNames(dockerCLI APIClientProvider) ValidArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
res, err := dockerCLI.Client().VolumeList(cmd.Context(), client.VolumeListOptions{})
list, err := dockerCLI.Client().VolumeList(cmd.Context(), volume.ListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, vol := range res.Items {
for _, vol := range list.Volumes {
names = append(names, vol.Name)
}
return names, cobra.ShellCompDirectiveNoFileComp
@ -123,14 +93,14 @@ func VolumeNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
}
// NetworkNames offers completion for networks
func NetworkNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
func NetworkNames(dockerCLI APIClientProvider) ValidArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
res, err := dockerCLI.Client().NetworkList(cmd.Context(), client.NetworkListOptions{})
list, err := dockerCLI.Client().NetworkList(cmd.Context(), network.ListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, nw := range res.Items {
for _, nw := range list {
names = append(names, nw.Name)
}
return names, cobra.ShellCompDirectiveNoFileComp
@ -150,33 +120,35 @@ func NetworkNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
// export MY_VAR=hello
// docker run --rm --env MY_VAR alpine printenv MY_VAR
// hello
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
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)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
// FromList offers completion for the given list of options.
func FromList(options ...string) cobra.CompletionFunc {
func FromList(options ...string) ValidArgsFn {
return cobra.FixedCompletions(options, cobra.ShellCompDirectiveNoFileComp)
}
// 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.CompletionFunc {
return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault
}
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
}
var commonPlatforms = []string{
"linux",
"linux/386",
"linux/amd64",
"linux/arm",
@ -193,8 +165,10 @@ var commonPlatforms = []string{
// Not yet supported
"linux/riscv64",
"windows",
"windows/amd64",
"wasip1",
"wasip1/wasm",
}
@ -213,8 +187,6 @@ 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.CompletionFunc {
return func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return commonPlatforms, cobra.ShellCompDirectiveNoFileComp
}
func Platforms(_ *cobra.Command, _ []string, _ string) (platforms []string, _ cobra.ShellCompDirective) {
return commonPlatforms, cobra.ShellCompDirectiveNoFileComp
}

View File

@ -6,11 +6,14 @@ import (
"sort"
"testing"
"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/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/volume"
"github.com/docker/docker/client"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -28,48 +31,48 @@ func (c fakeCLI) Client() client.APIClient {
type fakeClient struct {
client.Client
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)
containerListFunc func(options container.ListOptions) ([]types.Container, 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)
}
func (c *fakeClient) ContainerList(ctx context.Context, options client.ContainerListOptions) (client.ContainerListResult, error) {
func (c *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]types.Container, error) {
if c.containerListFunc != nil {
return c.containerListFunc(ctx, options)
return c.containerListFunc(options)
}
return client.ContainerListResult{}, nil
return []types.Container{}, nil
}
func (c *fakeClient) ImageList(ctx context.Context, options client.ImageListOptions) (client.ImageListResult, error) {
func (c *fakeClient) ImageList(_ context.Context, options image.ListOptions) ([]image.Summary, error) {
if c.imageListFunc != nil {
return c.imageListFunc(ctx, options)
return c.imageListFunc(options)
}
return client.ImageListResult{}, nil
return []image.Summary{}, nil
}
func (c *fakeClient) NetworkList(ctx context.Context, options client.NetworkListOptions) (client.NetworkListResult, error) {
func (c *fakeClient) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
if c.networkListFunc != nil {
return c.networkListFunc(ctx, options)
}
return client.NetworkListResult{}, nil
return []network.Inspect{}, nil
}
func (c *fakeClient) VolumeList(ctx context.Context, options client.VolumeListOptions) (client.VolumeListResult, error) {
func (c *fakeClient) VolumeList(_ context.Context, options volume.ListOptions) (volume.ListResponse, error) {
if c.volumeListFunc != nil {
return c.volumeListFunc(ctx, options)
return c.volumeListFunc(options.Filters)
}
return client.VolumeListResult{}, nil
return volume.ListResponse{}, nil
}
func TestCompleteContainerNames(t *testing.T) {
tests := []struct {
doc string
showAll, showIDs bool
filters []func(container.Summary) bool
containers []container.Summary
filters []func(types.Container) bool
containers []types.Container
expOut []string
expOpts client.ContainerListOptions
expOpts container.ListOptions
expDirective cobra.ShellCompDirective
}{
{
@ -79,33 +82,33 @@ func TestCompleteContainerNames(t *testing.T) {
{
doc: "all containers",
showAll: true,
containers: []container.Summary{
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b"}},
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b"}},
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
containers: []types.Container{
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
{ID: "id-b", State: "created", Names: []string{"/container-b"}},
{ID: "id-a", State: "exited", Names: []string{"/container-a"}},
},
expOut: []string{"container-c", "container-c/link-b", "container-b", "container-a"},
expOpts: client.ContainerListOptions{All: true},
expOpts: container.ListOptions{All: true},
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
doc: "all containers with ids",
showAll: true,
showIDs: true,
containers: []container.Summary{
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b"}},
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b"}},
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
containers: []types.Container{
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
{ID: "id-b", State: "created", Names: []string{"/container-b"}},
{ID: "id-a", State: "exited", Names: []string{"/container-a"}},
},
expOut: []string{"id-c", "container-c", "container-c/link-b", "id-b", "container-b", "id-a", "container-a"},
expOpts: client.ContainerListOptions{All: true},
expOpts: container.ListOptions{All: true},
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
doc: "only running containers",
showAll: false,
containers: []container.Summary{
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b"}},
containers: []types.Container{
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
},
expOut: []string{"container-c", "container-c/link-b"},
expDirective: cobra.ShellCompDirectiveNoFileComp,
@ -113,32 +116,32 @@ func TestCompleteContainerNames(t *testing.T) {
{
doc: "with filter",
showAll: true,
filters: []func(container.Summary) bool{
func(ctr container.Summary) bool { return ctr.State == container.StateCreated },
filters: []func(types.Container) bool{
func(container types.Container) bool { return container.State == "created" },
},
containers: []container.Summary{
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b"}},
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b"}},
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
containers: []types.Container{
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
{ID: "id-b", State: "created", Names: []string{"/container-b"}},
{ID: "id-a", State: "exited", Names: []string{"/container-a"}},
},
expOut: []string{"container-b"},
expOpts: client.ContainerListOptions{All: true},
expOpts: container.ListOptions{All: true},
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
doc: "multiple filters",
showAll: true,
filters: []func(container.Summary) bool{
func(ctr container.Summary) bool { return ctr.ID == "id-a" },
func(ctr container.Summary) bool { return ctr.State == container.StateCreated },
filters: []func(types.Container) bool{
func(container types.Container) bool { return container.ID == "id-a" },
func(container types.Container) bool { return container.State == "created" },
},
containers: []container.Summary{
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b"}},
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b"}},
{ID: "id-a", State: container.StateCreated, Names: []string{"/container-a"}},
containers: []types.Container{
{ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}},
{ID: "id-b", State: "created", Names: []string{"/container-b"}},
{ID: "id-a", State: "created", Names: []string{"/container-a"}},
},
expOut: []string{"container-a"},
expOpts: client.ContainerListOptions{All: true},
expOpts: container.ListOptions{All: true},
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
@ -148,17 +151,18 @@ func TestCompleteContainerNames(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
if tc.showIDs {
t.Setenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS", "yes")
}
comp := ContainerNames(fakeCLI{&fakeClient{
containerListFunc: func(_ context.Context, opts client.ContainerListOptions) (client.ContainerListResult, error) {
assert.Check(t, is.DeepEqual(opts, tc.expOpts))
containerListFunc: func(opts container.ListOptions) ([]types.Container, error) {
assert.Check(t, is.DeepEqual(opts, tc.expOpts, cmpopts.IgnoreUnexported(container.ListOptions{}, filters.Args{})))
if tc.expDirective == cobra.ShellCompDirectiveError {
return client.ContainerListResult{}, errors.New("some error occurred")
return nil, errors.New("some error occurred")
}
return client.ContainerListResult{Items: tc.containers}, nil
return tc.containers, nil
},
}}, tc.showAll, tc.filters...)
@ -174,7 +178,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)
@ -183,7 +187,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))
}
@ -224,15 +228,16 @@ func TestCompleteImageNames(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
comp := ImageNames(fakeCLI{&fakeClient{
imageListFunc: func(context.Context, client.ImageListOptions) (client.ImageListResult, error) {
imageListFunc: func(options image.ListOptions) ([]image.Summary, error) {
if tc.expDirective == cobra.ShellCompDirectiveError {
return client.ImageListResult{}, errors.New("some error occurred")
return nil, errors.New("some error occurred")
}
return client.ImageListResult{Items: tc.images}, nil
return tc.images, nil
},
}}, -1)
}})
volumes, directives := comp(&cobra.Command{}, nil, "")
assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective))
@ -255,24 +260,9 @@ func TestCompleteNetworkNames(t *testing.T) {
{
doc: "with results",
networks: []network.Summary{
{
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",
},
},
{ID: "nw-c", Name: "network-c"},
{ID: "nw-b", Name: "network-b"},
{ID: "nw-a", Name: "network-a"},
},
expOut: []string{"network-c", "network-b", "network-a"},
expDirective: cobra.ShellCompDirectiveNoFileComp,
@ -284,13 +274,14 @@ func TestCompleteNetworkNames(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
comp := NetworkNames(fakeCLI{&fakeClient{
networkListFunc: func(context.Context, client.NetworkListOptions) (client.NetworkListResult, error) {
networkListFunc: func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
if tc.expDirective == cobra.ShellCompDirectiveError {
return client.NetworkListResult{}, errors.New("some error occurred")
return nil, errors.New("some error occurred")
}
return client.NetworkListResult{Items: tc.networks}, nil
return tc.networks, nil
},
}})
@ -301,8 +292,14 @@ 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))
}
@ -310,7 +307,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
}{
@ -320,7 +317,7 @@ func TestCompleteVolumeNames(t *testing.T) {
},
{
doc: "with results",
volumes: []volume.Volume{
volumes: []*volume.Volume{
{Name: "volume-c"},
{Name: "volume-b"},
{Name: "volume-a"},
@ -335,13 +332,14 @@ func TestCompleteVolumeNames(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
comp := VolumeNames(fakeCLI{&fakeClient{
volumeListFunc: func(context.Context, client.VolumeListOptions) (client.VolumeListResult, error) {
volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) {
if tc.expDirective == cobra.ShellCompDirectiveError {
return client.VolumeListResult{}, errors.New("some error occurred")
return volume.ListResponse{}, errors.New("some error occurred")
}
return client.VolumeListResult{Items: tc.volumes}, nil
return volume.ListResponse{Volumes: tc.volumes}, nil
},
}})

View File

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

View File

@ -4,46 +4,40 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/internal/commands"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
)
func init() {
commands.Register(newConfigCommand)
}
// newConfigCommand returns a cobra command for `config` subcommands
func newConfigCommand(dockerCLI command.Cli) *cobra.Command {
// 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
}
// completeNames offers completion for swarm configs
func completeNames(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
func completeNames(dockerCLI completion.APIClientProvider) completion.ValidArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
res, err := dockerCLI.Client().ConfigList(cmd.Context(), client.ConfigListOptions{})
list, err := dockerCLI.Client().ConfigList(cmd.Context(), types.ConfigListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, config := range res.Items {
for _, config := range list {
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/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/swarm"
"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,113 +33,56 @@ 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 runCreate(cmd.Context(), dockerCLI, createOpts)
createOpts.Name = args[0]
createOpts.File = args[1]
return RunConfigCreate(cmd.Context(), dockerCli, createOpts)
},
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,
ValidArgsFunction: completion.NoComplete,
}
flags := cmd.Flags()
flags.VarP(&createOpts.labels, "label", "l", "Config labels")
flags.StringVar(&createOpts.templateDriver, "template-driver", "", "Template driver")
_ = flags.SetAnnotation("template-driver", "version", []string{"1.37"})
flags.VarP(&createOpts.Labels, "label", "l", "Config labels")
flags.StringVar(&createOpts.TemplateDriver, "template-driver", "", "Template driver")
flags.SetAnnotation("template-driver", "version", []string{"1.37"})
return cmd
}
// runCreate creates a config with the given options.
func runCreate(ctx context.Context, dockerCLI command.Cli, options createOptions) error {
apiClient := dockerCLI.Client()
// RunConfigCreate creates a config with the given options.
func RunConfigCreate(ctx context.Context, dockerCli command.Cli, options CreateOptions) error {
client := dockerCli.Client()
configData, err := readConfigData(dockerCLI.In(), options.file)
var in io.Reader = dockerCli.In()
if options.File != "-" {
file, err := sequential.Open(options.File)
if err != nil {
return err
}
in = file
defer file.Close()
}
configData, err := io.ReadAll(in)
if err != nil {
return fmt.Errorf("error reading content from %q: %v", options.file, err)
return errors.Errorf("Error reading content from %q: %v", options.File, err)
}
spec := swarm.ConfigSpec{
Annotations: swarm.Annotations{
Name: options.name,
Labels: opts.ConvertKVStringsToMap(options.labels.GetSlice()),
Name: options.Name,
Labels: opts.ConvertKVStringsToMap(options.Labels.GetAll()),
},
Data: configData,
}
if options.templateDriver != "" {
if options.TemplateDriver != "" {
spec.Templating = &swarm.Driver{
Name: options.templateDriver,
Name: options.TemplateDriver,
}
}
r, err := apiClient.ConfigCreate(ctx, client.ConfigCreateOptions{
Spec: spec,
})
r, err := client.ConfigCreate(ctx, spec)
if err != nil {
return err
}
_, _ = fmt.Fprintln(dockerCLI.Out(), r.ID)
fmt.Fprintln(dockerCli.Out(), r.ID)
return nil
}
// maxConfigSize is the maximum byte length of the [swarm.ConfigSpec.Data] field,
// as defined by [MaxConfigSize] in SwarmKit.
//
// [MaxConfigSize]: https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/manager/controlapi#MaxConfigSize
const maxConfigSize = 1000 * 1024 // 1000KB
// readConfigData reads the config from either stdin or the given fileName.
//
// It reads up to twice the maximum size of the config ([maxConfigSize]),
// just in case swarm's limit changes; this is only a safeguard to prevent
// reading arbitrary files into memory.
func readConfigData(in io.Reader, fileName string) ([]byte, error) {
switch fileName {
case "-":
data, err := io.ReadAll(io.LimitReader(in, 2*maxConfigSize))
if err != nil {
return nil, fmt.Errorf("error reading from STDIN: %w", err)
}
if len(data) == 0 {
return nil, errors.New("error reading from STDIN: data is empty")
}
return data, nil
case "":
return nil, errors.New("config file is required")
default:
// Open file with [FILE_FLAG_SEQUENTIAL_SCAN] on Windows, which
// prevents Windows from aggressively caching it. We expect this
// file to be only read once. Given that this is expected to be
// a small file, this may not be a significant optimization, so
// we could choose to omit this, and use a regular [os.Open].
//
// [FILE_FLAG_SEQUENTIAL_SCAN]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN
f, err := sequential.Open(fileName)
if err != nil {
return nil, fmt.Errorf("error reading from %s: %w", fileName, err)
}
defer f.Close()
data, err := io.ReadAll(io.LimitReader(f, 2*maxConfigSize))
if err != nil {
return nil, fmt.Errorf("error reading from %s: %w", fileName, err)
}
if len(data) == 0 {
return nil, fmt.Errorf("error reading from %s: data is empty", fileName)
}
return data, nil
}
}

View File

@ -2,8 +2,6 @@ package config
import (
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
@ -12,8 +10,9 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
@ -24,26 +23,27 @@ const configDataFile = "config-create-with-name.golden"
func TestConfigCreateErrors(t *testing.T) {
testCases := []struct {
args []string
configCreateFunc func(context.Context, client.ConfigCreateOptions) (client.ConfigCreateResult, error)
configCreateFunc func(context.Context, swarm.ConfigSpec) (types.ConfigCreateResponse, error)
expectedError string
}{
{
args: []string{"too_few"},
expectedError: "requires 2 arguments",
expectedError: "requires exactly 2 arguments",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires 2 arguments",
expectedError: "requires exactly 2 arguments",
},
{
args: []string{"name", filepath.Join("testdata", configDataFile)},
configCreateFunc: func(_ context.Context, options client.ConfigCreateOptions) (client.ConfigCreateResult, error) {
return client.ConfigCreateResult{}, errors.New("error creating config")
configCreateFunc: func(_ context.Context, configSpec swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
return types.ConfigCreateResponse{}, errors.Errorf("error creating config")
},
expectedError: "error creating config",
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.expectedError, func(t *testing.T) {
cmd := newConfigCreateCommand(
test.NewFakeCli(&fakeClient{
@ -59,18 +59,18 @@ func TestConfigCreateErrors(t *testing.T) {
}
func TestConfigCreateWithName(t *testing.T) {
const name = "config-with-name"
name := "foo"
var actual []byte
cli := test.NewFakeCli(&fakeClient{
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)
configCreateFunc: func(_ context.Context, spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
if spec.Name != name {
return types.ConfigCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name)
}
actual = options.Spec.Data
actual = spec.Data
return client.ConfigCreateResult{
ID: "ID-" + options.Spec.Name,
return types.ConfigCreateResponse{
ID: "ID-" + spec.Name,
}, nil
},
})
@ -87,7 +87,7 @@ func TestConfigCreateWithLabels(t *testing.T) {
"lbl1": "Label-foo",
"lbl2": "Label-bar",
}
const name = "config-with-labels"
name := "foo"
data, err := os.ReadFile(filepath.Join("testdata", configDataFile))
assert.NilError(t, err)
@ -101,13 +101,13 @@ func TestConfigCreateWithLabels(t *testing.T) {
}
cli := test.NewFakeCli(&fakeClient{
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)
configCreateFunc: func(_ context.Context, spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
if !reflect.DeepEqual(spec, expected) {
return types.ConfigCreateResponse{}, errors.Errorf("expected %+v, got %+v", expected, spec)
}
return client.ConfigCreateResult{
ID: "ID-" + options.Spec.Name,
return types.ConfigCreateResponse{
ID: "ID-" + spec.Name,
}, nil
},
})
@ -124,20 +124,20 @@ func TestConfigCreateWithTemplatingDriver(t *testing.T) {
expectedDriver := &swarm.Driver{
Name: "template-driver",
}
const name = "config-with-template-driver"
name := "foo"
cli := test.NewFakeCli(&fakeClient{
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)
configCreateFunc: func(_ context.Context, spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
if spec.Name != name {
return types.ConfigCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name)
}
if options.Spec.Templating.Name != expectedDriver.Name {
return client.ConfigCreateResult{}, fmt.Errorf("expected driver %v, got %v", expectedDriver, options.Spec.Labels)
if spec.Templating.Name != expectedDriver.Name {
return types.ConfigCreateResponse{}, errors.Errorf("expected driver %v, got %v", expectedDriver, spec.Labels)
}
return client.ConfigCreateResult{
ID: "ID-" + options.Spec.Name,
return types.ConfigCreateResponse{
ID: "ID-" + spec.Name,
}, nil
},
})

View File

@ -5,11 +5,11 @@ import (
"strings"
"time"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/inspect"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/swarm"
units "github.com/docker/go-units"
)
const (
@ -30,8 +30,8 @@ Data:
{{.Data}}`
)
// newFormat returns a Format for rendering using a configContext.
func newFormat(source string, quiet bool) formatter.Format {
// NewFormat returns a Format for rendering using a config Context
func NewFormat(source string, quiet bool) formatter.Format {
switch source {
case formatter.PrettyFormatKey:
return configInspectPrettyTemplate
@ -44,28 +44,31 @@ func newFormat(source string, quiet bool) formatter.Format {
return formatter.Format(source)
}
// 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 {
// 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 {
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 {
@ -112,12 +115,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(fmtCtx formatter.Context, refs []string, getRef inspect.GetRefFunc) error {
if fmtCtx.Format != configInspectPrettyTemplate {
return inspect.Inspect(fmtCtx.Output, refs, string(fmtCtx.Format), getRef)
// 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)
}
return fmtCtx.Write(&configInspectContext{}, func(format func(subContext formatter.SubContext) error) error {
render := func(format func(subContext formatter.SubContext) error) error {
for _, ref := range refs {
configI, _, err := getRef(ref)
if err != nil {
@ -132,7 +135,8 @@ func inspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef inspect.
}
}
return nil
})
}
return ctx.Write(&configInspectContext{}, render)
}
type configInspectContext struct {
@ -153,11 +157,11 @@ func (ctx *configInspectContext) Labels() map[string]string {
}
func (ctx *configInspectContext) CreatedAt() string {
return formatter.PrettyPrint(ctx.Config.CreatedAt)
return command.PrettyPrint(ctx.Config.CreatedAt)
}
func (ctx *configInspectContext) UpdatedAt() string {
return formatter.PrettyPrint(ctx.Config.UpdatedAt)
return command.PrettyPrint(ctx.Config.UpdatedAt)
}
func (ctx *configInspectContext) Data() string {

View File

@ -6,8 +6,7 @@ import (
"time"
"github.com/docker/cli/cli/command/formatter"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/swarm"
"gotest.tools/v3/assert"
)
@ -28,46 +27,45 @@ 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
`,
},
}
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"}},
},
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"}},
},
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out
if err := formatWrite(tc.context, res); err != nil {
if err := FormatWrite(tc.context, configs); 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.24
//go:build go1.22
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 runInspect(cmd.Context(), dockerCLI, opts)
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)
},
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
}
// runInspect inspects the given Swarm config.
func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error {
apiClient := dockerCLI.Client()
// RunConfigInspect inspects the given Swarm config.
func RunConfigInspect(ctx context.Context, dockerCli command.Cli, opts InspectOptions) error {
client := dockerCli.Client()
if opts.pretty {
opts.format = "pretty"
if opts.Pretty {
opts.Format = "pretty"
}
getRef := func(id string) (any, []byte, error) {
res, err := apiClient.ConfigInspect(ctx, id, client.ConfigInspectOptions{})
return res.Config, res.Raw, err
return client.ConfigInspectWithRaw(ctx, id)
}
f := opts.Format
// check if the user is trying to apply a template to the pretty format, which
// is not supported
if strings.HasPrefix(opts.format, "pretty") && opts.format != "pretty" {
if strings.HasPrefix(f, "pretty") && f != "pretty" {
return errors.New("cannot supply extra formatting options to the pretty template")
}
configCtx := formatter.Context{
Output: dockerCLI.Out(),
Format: newFormat(opts.format, false),
Output: dockerCli.Out(),
Format: NewFormat(f, false),
}
if err := inspectFormatWrite(configCtx, opts.names, getRef); err != nil {
if err := InspectFormatWrite(configCtx, opts.Names, getRef); err != nil {
return cli.StatusError{StatusCode: 1, Status: err.Error()}
}
return nil

View File

@ -2,7 +2,6 @@ package config
import (
"context"
"errors"
"fmt"
"io"
"testing"
@ -10,7 +9,8 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"
"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, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error)
configInspectFunc func(_ context.Context, configID string) (swarm.Config, []byte, error)
expectedError string
}{
{
@ -27,8 +27,8 @@ func TestConfigInspectErrors(t *testing.T) {
},
{
args: []string{"foo"},
configInspectFunc: func(context.Context, string, client.ConfigInspectOptions) (client.ConfigInspectResult, error) {
return client.ConfigInspectResult{}, errors.New("error while inspecting the config")
configInspectFunc: func(_ context.Context, configID string) (swarm.Config, []byte, error) {
return swarm.Config{}, nil, errors.Errorf("error while inspecting the config")
},
expectedError: "error while inspecting the config",
},
@ -41,13 +41,11 @@ func TestConfigInspectErrors(t *testing.T) {
},
{
args: []string{"foo", "bar"},
configInspectFunc: func(_ context.Context, configID string, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error) {
configInspectFunc: func(_ context.Context, configID string) (swarm.Config, []byte, error) {
if configID == "foo" {
return client.ConfigInspectResult{
Config: *builders.Config(builders.ConfigName("foo")),
}, nil
return *builders.Config(builders.ConfigName("foo")), nil, nil
}
return client.ConfigInspectResult{}, errors.New("error while inspecting the config")
return swarm.Config{}, nil, errors.Errorf("error while inspecting the config")
},
expectedError: "error while inspecting the config",
},
@ -72,34 +70,25 @@ func TestConfigInspectWithoutFormat(t *testing.T) {
testCases := []struct {
name string
args []string
configInspectFunc func(_ context.Context, configID string, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error)
configInspectFunc func(_ context.Context, configID string) (swarm.Config, []byte, error)
}{
{
name: "single-config",
args: []string{"foo"},
configInspectFunc: func(_ context.Context, name string, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error) {
configInspectFunc: func(_ context.Context, name string) (swarm.Config, []byte, error) {
if name != "foo" {
return client.ConfigInspectResult{}, fmt.Errorf("invalid name, expected %s, got %s", "foo", name)
return swarm.Config{}, nil, errors.Errorf("Invalid name, expected %s, got %s", "foo", name)
}
return client.ConfigInspectResult{
Config: *builders.Config(
builders.ConfigID("ID-foo"),
builders.ConfigName("foo"),
),
}, nil
return *builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")), nil, nil
},
},
{
name: "multiple-configs-with-labels",
args: []string{"foo", "bar"},
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
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
},
},
}
@ -113,19 +102,16 @@ func TestConfigInspectWithoutFormat(t *testing.T) {
}
func TestConfigInspectWithFormat(t *testing.T) {
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
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
}
testCases := []struct {
name string
format string
args []string
configInspectFunc func(_ context.Context, name string, _ client.ConfigInspectOptions) (client.ConfigInspectResult, error)
configInspectFunc func(_ context.Context, name string) (swarm.Config, []byte, error)
}{
{
name: "simple-template",
@ -155,23 +141,21 @@ func TestConfigInspectWithFormat(t *testing.T) {
func TestConfigInspectPretty(t *testing.T) {
testCases := []struct {
name string
configInspectFunc func(context.Context, string, client.ConfigInspectOptions) (client.ConfigInspectResult, error)
configInspectFunc func(context.Context, string) (swarm.Config, []byte, error)
}{
{
name: "simple",
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
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
},
},
}

View File

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

View File

@ -2,7 +2,6 @@ package config
import (
"context"
"errors"
"io"
"testing"
"time"
@ -10,16 +9,18 @@ import (
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"
"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, client.ConfigListOptions) (client.ConfigListResult, error)
configListFunc func(context.Context, types.ConfigListOptions) ([]swarm.Config, error)
expectedError string
}{
{
@ -27,8 +28,8 @@ func TestConfigListErrors(t *testing.T) {
expectedError: "accepts no argument",
},
{
configListFunc: func(_ context.Context, options client.ConfigListOptions) (client.ConfigListResult, error) {
return client.ConfigListResult{}, errors.New("error listing configs")
configListFunc: func(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{}, errors.Errorf("error listing configs")
},
expectedError: "error listing configs",
},
@ -48,28 +49,26 @@ func TestConfigListErrors(t *testing.T) {
func TestConfigList(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
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)),
),
},
configListFunc: func(_ context.Context, options types.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)),
),
}, nil
},
})
@ -80,14 +79,12 @@ func TestConfigList(t *testing.T) {
func TestConfigListWithQuietOption(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
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",
})),
},
configListFunc: func(_ context.Context, options types.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",
})),
}, nil
},
})
@ -99,14 +96,12 @@ func TestConfigListWithQuietOption(t *testing.T) {
func TestConfigListWithConfigFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
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",
})),
},
configListFunc: func(_ context.Context, options types.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",
})),
}, nil
},
})
@ -120,14 +115,12 @@ func TestConfigListWithConfigFormat(t *testing.T) {
func TestConfigListWithFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
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",
})),
},
configListFunc: func(_ context.Context, options types.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",
})),
}, nil
},
})
@ -139,24 +132,22 @@ func TestConfigListWithFormat(t *testing.T) {
func TestConfigListWithFilter(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
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)),
),
},
configListFunc: func(_ context.Context, options types.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)),
),
}, nil
},
})

View File

@ -2,41 +2,56 @@ package config
import (
"context"
"errors"
"fmt"
"strings"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/moby/moby/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
func newConfigRemoveCommand(dockerCLI command.Cli) *cobra.Command {
// RemoveOptions contains options for the docker config rm command.
type RemoveOptions struct {
Names []string
}
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 {
return runRemove(cmd.Context(), dockerCLI, args)
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)
},
ValidArgsFunction: completeNames(dockerCLI),
DisableFlagsInUseLine: true,
}
}
// runRemove removes the given Swarm configs.
func runRemove(ctx context.Context, dockerCLI command.Cli, names []string) error {
apiClient := dockerCLI.Client()
// RunConfigRemove removes the given Swarm configs.
func RunConfigRemove(ctx context.Context, dockerCli command.Cli, opts RemoveOptions) error {
client := dockerCli.Client()
var errs []error
for _, name := range names {
if _, err := apiClient.ConfigRemove(ctx, name, client.ConfigRemoveOptions{}); err != nil {
errs = append(errs, err)
var errs []string
for _, name := range opts.Names {
if err := client.ConfigRemove(ctx, name); err != nil {
errs = append(errs, err.Error())
continue
}
_, _ = fmt.Fprintln(dockerCLI.Out(), name)
fmt.Fprintln(dockerCli.Out(), name)
}
return errors.Join(errs...)
if len(errs) > 0 {
return errors.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

View File

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

View File

@ -2,15 +2,16 @@ 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/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/moby/sys/signal"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@ -22,26 +23,26 @@ type AttachOptions struct {
DetachKeys string
}
func inspectContainerAndCheckState(ctx context.Context, apiClient client.APIClient, args string) (*container.InspectResponse, error) {
c, err := apiClient.ContainerInspect(ctx, args, client.ContainerInspectOptions{})
func inspectContainerAndCheckState(ctx context.Context, apiClient client.APIClient, args string) (*types.ContainerJSON, error) {
c, err := apiClient.ContainerInspect(ctx, args)
if err != nil {
return nil, err
}
if !c.Container.State.Running {
return nil, errors.New("cannot attach to a stopped container, start it first")
if !c.State.Running {
return nil, errors.New("You cannot attach to a stopped container, start it first")
}
if c.Container.State.Paused {
return nil, errors.New("cannot attach to a paused container, unpause it first")
if c.State.Paused {
return nil, errors.New("You cannot attach to a paused container, unpause it first")
}
if c.Container.State.Restarting {
return nil, errors.New("cannot attach to a restarting container, wait until it is running")
if c.State.Restarting {
return nil, errors.New("You cannot attach to a restarting container, wait until it is running")
}
return &c.Container, nil
return &c, 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{
@ -55,10 +56,9 @@ func newAttachCommand(dockerCLI command.Cli) *cobra.Command {
Annotations: map[string]string{
"aliases": "docker container attach, docker attach",
},
ValidArgsFunction: completion.ContainerNames(dockerCLI, false, func(ctr container.Summary) bool {
return ctr.State != container.StatePaused
ValidArgsFunction: completion.ContainerNames(dockerCLI, false, func(ctr types.Container) bool {
return ctr.State != "paused"
}),
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -74,7 +74,7 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
// request channel to wait for client
waitCtx := context.WithoutCancel(ctx)
waitRes := apiClient.ContainerWait(waitCtx, containerID, client.ContainerWaitOptions{})
resultC, errC := apiClient.ContainerWait(waitCtx, containerID, "")
c, err := inspectContainerAndCheckState(ctx, apiClient, containerID)
if err != nil {
@ -90,7 +90,7 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
detachKeys = opts.DetachKeys
}
options := client.ContainerAttachOptions{
options := container.AttachOptions{
Stream: true,
Stdin: !opts.NoStdin && c.Config.OpenStdin,
Stdout: true,
@ -114,11 +114,11 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
defer signal.StopCatch(sigc)
}
res, err := apiClient.ContainerAttach(ctx, containerID, options)
if err != nil {
return err
resp, errAttach := apiClient.ContainerAttach(ctx, containerID, options)
if errAttach != nil {
return errAttach
}
defer res.HijackedResponse.Close()
defer resp.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
@ -142,7 +142,7 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
inputStream: in,
outputStream: dockerCLI.Out(),
errorStream: dockerCLI.Err(),
resp: res.HijackedResponse,
resp: resp,
tty: c.Config.Tty,
detachKeys: options.DetachKeys,
}
@ -152,19 +152,22 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
return err
}
return getExitStatus(waitRes)
return getExitStatus(errC, resultC)
}
func getExitStatus(waitRes client.ContainerWaitResult) error {
func getExitStatus(errC <-chan error, resultC <-chan container.WaitResponse) error {
select {
case result := <-waitRes.Result:
case result := <-resultC:
if result.Error != nil {
return errors.New(result.Error.Message)
}
if result.StatusCode != 0 {
return cli.StatusError{StatusCode: int(result.StatusCode)}
}
case err := <-waitRes.Error:
case err := <-errC:
if errors.Is(err, context.Canceled) {
return nil
}
return err
}
@ -177,7 +180,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

@ -1,14 +1,15 @@
package container
import (
"errors"
"context"
"io"
"testing"
"github.com/docker/cli/cli"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
)
@ -17,65 +18,61 @@ func TestNewAttachCommandErrors(t *testing.T) {
name string
args []string
expectedError string
containerInspectFunc func(img string) (client.ContainerInspectResult, error)
containerInspectFunc func(img string) (types.ContainerJSON, error)
}{
{
name: "client-error",
args: []string{"5cb5bb5e4a3b"},
expectedError: "something went wrong",
containerInspectFunc: func(containerID string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{}, errors.New("something went wrong")
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
return types.ContainerJSON{}, errors.Errorf("something went wrong")
},
},
{
name: "client-stopped",
args: []string{"5cb5bb5e4a3b"},
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,
},
},
}, nil
expectedError: "You cannot attach to a stopped container",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
c := types.ContainerJSON{}
c.ContainerJSONBase = &types.ContainerJSONBase{}
c.ContainerJSONBase.State = &types.ContainerState{Running: false}
return c, nil
},
},
{
name: "client-paused",
args: []string{"5cb5bb5e4a3b"},
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,
},
},
}, nil
expectedError: "You cannot attach to a paused container",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
c := types.ContainerJSON{}
c.ContainerJSONBase = &types.ContainerJSONBase{}
c.ContainerJSONBase.State = &types.ContainerState{
Running: true,
Paused: true,
}
return c, nil
},
},
{
name: "client-restarting",
args: []string{"5cb5bb5e4a3b"},
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,
Restarting: true,
},
},
}, nil
expectedError: "You cannot attach to a restarting container",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
c := types.ContainerJSON{}
c.ContainerJSONBase = &types.ContainerJSONBase{}
c.ContainerJSONBase.State = &types.ContainerState{
Running: true,
Paused: false,
Restarting: true,
}
return c, nil
},
},
}
for _, tc := range testCases {
tc := tc
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)
@ -113,6 +110,10 @@ func TestGetExitStatus(t *testing.T) {
},
expectedError: cli.StatusError{StatusCode: 15},
},
{
err: context.Canceled,
expectedError: nil,
},
}
for _, testcase := range testcases {
@ -125,10 +126,7 @@ func TestGetExitStatus(t *testing.T) {
resultC <- *testcase.result
}
err := getExitStatus(client.ContainerWaitResult{
Result: resultC,
Error: errC,
})
err := getExitStatus(errC, resultC)
if testcase.expectedError == nil {
assert.NilError(t, err)

View File

@ -1,46 +0,0 @@
package container
import (
"fmt"
"os"
"strings"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/types"
)
// readCredentials resolves auth-config from the current environment to be
// applied to the container if the `--use-api-socket` flag is set.
//
// - If a valid "DOCKER_AUTH_CONFIG" env-var is found, and it contains
// credentials, it's value is used.
// - If no "DOCKER_AUTH_CONFIG" env-var is found, or it does not contain
// credentials, it attempts to read from the CLI's credentials store.
//
// It returns an error if either the "DOCKER_AUTH_CONFIG" is incorrectly
// formatted, or when failing to read from the credentials store.
//
// A nil value is returned if neither option contained any credentials.
func readCredentials(dockerCLI config.Provider) (creds map[string]types.AuthConfig, _ error) {
if v, ok := os.LookupEnv("DOCKER_AUTH_CONFIG"); ok && v != "" {
// The results are expected to have been unmarshaled the same as
// when reading from a config-file, which includes decoding the
// base64-encoded "username:password" into the "UserName" and
// "Password" fields.
ac := &configfile.ConfigFile{}
if err := ac.LoadFromReader(strings.NewReader(v)); err != nil {
return nil, fmt.Errorf("failed to read credentials from DOCKER_AUTH_CONFIG: %w", err)
}
if len(ac.AuthConfigs) > 0 {
return ac.AuthConfigs, nil
}
}
// Resolve this here for later, ensuring we error our before we create the container.
creds, err := dockerCLI.ConfigFile().GetAllCredentials()
if err != nil {
return nil, fmt.Errorf("resolving credentials failed: %w", err)
}
return creds, nil
}

View File

@ -3,232 +3,181 @@ package container
import (
"context"
"io"
"net/http"
"strings"
"github.com/moby/moby/client"
"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"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
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) (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 client.ContainerCommitOptions) (client.ContainerCommitResult, error)
containerPauseFunc func(ctx context.Context, container string, options client.ContainerPauseOptions) (client.ContainerPauseResult, error)
inspectFunc func(string) (types.ContainerJSON, error)
execInspectFunc func(execID string) (container.ExecInspect, error)
execCreateFunc func(containerID string, options container.ExecOptions) (types.IDResponse, error)
createContainerFunc func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *specs.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) ([]types.Container, 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
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)
Version string
}
func (f *fakeClient) ContainerList(_ context.Context, options client.ContainerListOptions) (client.ContainerListResult, error) {
func (f *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]types.Container, error) {
if f.containerListFunc != nil {
return f.containerListFunc(options)
}
return client.ContainerListResult{}, nil
return []types.Container{}, nil
}
func (f *fakeClient) ContainerInspect(_ context.Context, containerID string, _ client.ContainerInspectOptions) (client.ContainerInspectResult, error) {
func (f *fakeClient) ContainerInspect(_ context.Context, containerID string) (types.ContainerJSON, error) {
if f.inspectFunc != nil {
return f.inspectFunc(containerID)
}
return client.ContainerInspectResult{}, nil
return types.ContainerJSON{}, nil
}
func (f *fakeClient) ExecCreate(_ context.Context, containerID string, config client.ExecCreateOptions) (client.ExecCreateResult, error) {
func (f *fakeClient) ContainerExecCreate(_ context.Context, containerID string, config container.ExecOptions) (types.IDResponse, error) {
if f.execCreateFunc != nil {
return f.execCreateFunc(containerID, config)
}
return client.ExecCreateResult{}, nil
return types.IDResponse{}, nil
}
func (f *fakeClient) ExecInspect(_ context.Context, execID string, _ client.ExecInspectOptions) (client.ExecInspectResult, error) {
func (f *fakeClient) ContainerExecInspect(_ context.Context, execID string) (container.ExecInspect, error) {
if f.execInspectFunc != nil {
return f.execInspectFunc(execID)
}
return client.ExecInspectResult{}, nil
return container.ExecInspect{}, nil
}
func (*fakeClient) ExecStart(context.Context, string, client.ExecStartOptions) (client.ExecStartResult, error) {
return client.ExecStartResult{}, nil
func (f *fakeClient) ContainerExecStart(context.Context, string, container.ExecStartOptions) error {
return nil
}
func (f *fakeClient) ContainerCreate(_ context.Context, options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
func (f *fakeClient) ContainerCreate(
_ context.Context,
config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *specs.Platform,
containerName string,
) (container.CreateResponse, error) {
if f.createContainerFunc != nil {
return f.createContainerFunc(options)
return f.createContainerFunc(config, hostConfig, networkingConfig, platform, containerName)
}
return client.ContainerCreateResult{}, nil
return container.CreateResponse{}, nil
}
func (f *fakeClient) ContainerRemove(ctx context.Context, containerID string, options client.ContainerRemoveOptions) (client.ContainerRemoveResult, error) {
func (f *fakeClient) ContainerRemove(ctx context.Context, containerID string, options container.RemoveOptions) error {
if f.containerRemoveFunc != nil {
return f.containerRemoveFunc(ctx, containerID, options)
}
return client.ContainerRemoveResult{}, nil
return nil
}
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)
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)
}
return fakeStreamResult{}, nil
return nil, nil
}
func (f *fakeClient) Info(context.Context, client.InfoOptions) (client.SystemInfoResult, error) {
func (f *fakeClient) Info(_ context.Context) (system.Info, error) {
if f.infoFunc != nil {
return f.infoFunc()
}
return client.SystemInfoResult{}, nil
return system.Info{}, nil
}
func (f *fakeClient) ContainerStatPath(_ context.Context, containerID string, options client.ContainerStatPathOptions) (client.ContainerStatPathResult, error) {
func (f *fakeClient) ContainerStatPath(_ context.Context, containerID, path string) (container.PathStat, error) {
if f.containerStatPathFunc != nil {
return f.containerStatPathFunc(containerID, options.Path)
return f.containerStatPathFunc(containerID, path)
}
return client.ContainerStatPathResult{}, nil
return container.PathStat{}, nil
}
func (f *fakeClient) CopyFromContainer(_ context.Context, containerID string, options client.CopyFromContainerOptions) (client.CopyFromContainerResult, error) {
func (f *fakeClient) CopyFromContainer(_ context.Context, containerID, srcPath string) (io.ReadCloser, container.PathStat, error) {
if f.containerCopyFromFunc != nil {
return f.containerCopyFromFunc(containerID, options.SourcePath)
return f.containerCopyFromFunc(containerID, srcPath)
}
return client.CopyFromContainerResult{}, nil
return nil, container.PathStat{}, nil
}
func (f *fakeClient) ContainerLogs(_ context.Context, containerID string, options client.ContainerLogsOptions) (client.ContainerLogsResult, error) {
func (f *fakeClient) ContainerLogs(_ context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
if f.logFunc != nil {
return f.logFunc(containerID, options)
}
return http.NoBody, nil
return nil, nil
}
func (f *fakeClient) ClientVersion() string {
return f.Version
}
func (f *fakeClient) ContainerWait(_ context.Context, containerID string, _ client.ContainerWaitOptions) client.ContainerWaitResult {
func (f *fakeClient) ContainerWait(_ context.Context, containerID string, _ container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
if f.waitFunc != nil {
return f.waitFunc(containerID)
}
return client.ContainerWaitResult{}
return nil, nil
}
func (f *fakeClient) ContainerStart(_ context.Context, containerID string, options client.ContainerStartOptions) (client.ContainerStartResult, error) {
func (f *fakeClient) ContainerStart(_ context.Context, containerID string, options container.StartOptions) error {
if f.containerStartFunc != nil {
return f.containerStartFunc(containerID, options)
}
return client.ContainerStartResult{}, nil
return nil
}
func (f *fakeClient) ContainerExport(_ context.Context, containerID string, _ client.ContainerExportOptions) (client.ContainerExportResult, error) {
func (f *fakeClient) ContainerExport(_ context.Context, containerID string) (io.ReadCloser, error) {
if f.containerExportFunc != nil {
return f.containerExportFunc(containerID)
}
return http.NoBody, nil
return nil, nil
}
func (f *fakeClient) ExecResize(_ context.Context, id string, options client.ExecResizeOptions) (client.ExecResizeResult, error) {
func (f *fakeClient) ContainerExecResize(_ context.Context, id string, options container.ResizeOptions) error {
if f.containerExecResizeFunc != nil {
return f.containerExecResizeFunc(id, options)
}
return client.ExecResizeResult{}, nil
return nil
}
func (f *fakeClient) ContainerKill(ctx context.Context, containerID string, options client.ContainerKillOptions) (client.ContainerKillResult, error) {
func (f *fakeClient) ContainerKill(ctx context.Context, containerID, signal string) error {
if f.containerKillFunc != nil {
return f.containerKillFunc(ctx, containerID, options)
return f.containerKillFunc(ctx, containerID, signal)
}
return client.ContainerKillResult{}, nil
return nil
}
func (f *fakeClient) ContainerPrune(ctx context.Context, options client.ContainerPruneOptions) (client.ContainerPruneResult, error) {
func (f *fakeClient) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
if f.containerPruneFunc != nil {
return f.containerPruneFunc(ctx, options)
return f.containerPruneFunc(ctx, pruneFilters)
}
return client.ContainerPruneResult{}, nil
return container.PruneReport{}, nil
}
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 client.ContainerRestartResult{}, nil
}
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 client.ContainerStopResult{}, nil
}
func (f *fakeClient) ContainerAttach(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.ContainerAttachResult, error) {
func (f *fakeClient) ContainerAttach(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
if f.containerAttachFunc != nil {
return f.containerAttachFunc(ctx, containerID, options)
}
return client.ContainerAttachResult{}, nil
}
func (f *fakeClient) ContainerDiff(ctx context.Context, containerID string, _ client.ContainerDiffOptions) (client.ContainerDiffResult, error) {
if f.containerDiffFunc != nil {
return f.containerDiffFunc(ctx, containerID)
}
return client.ContainerDiffResult{}, nil
}
func (f *fakeClient) ContainerRename(ctx context.Context, oldName string, options client.ContainerRenameOptions) (client.ContainerRenameResult, error) {
if f.containerRenameFunc != nil {
return client.ContainerRenameResult{}, f.containerRenameFunc(ctx, oldName, options.NewName)
}
return client.ContainerRenameResult{}, nil
}
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 client.ContainerCommitResult{}, nil
}
func (f *fakeClient) ContainerPause(ctx context.Context, containerID string, options client.ContainerPauseOptions) (client.ContainerPauseResult, error) {
if f.containerPauseFunc != nil {
return f.containerPauseFunc(ctx, containerID, options)
}
return client.ContainerPauseResult{}, nil
return types.HijackedResponse{}, nil
}

View File

@ -3,73 +3,43 @@ package container
import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/internal/commands"
"github.com/spf13/cobra"
)
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 {
// 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()),
DisableFlagsInUseLine: true,
RunE: command.ShowHelp(dockerCli.Err()),
}
cmd.AddCommand(
newAttachCommand(dockerCLI),
newCommitCommand(dockerCLI),
newCopyCommand(dockerCLI),
newCreateCommand(dockerCLI),
newDiffCommand(dockerCLI),
newExecCommand(dockerCLI),
newExportCommand(dockerCLI),
newKillCommand(dockerCLI),
newLogsCommand(dockerCLI),
newPauseCommand(dockerCLI),
newPortCommand(dockerCLI),
newRenameCommand(dockerCLI),
newRestartCommand(dockerCLI),
newRemoveCommand(dockerCLI),
newRunCommand(dockerCLI),
newStartCommand(dockerCLI),
newStatsCommand(dockerCLI),
newStopCommand(dockerCLI),
newTopCommand(dockerCLI),
newUnpauseCommand(dockerCLI),
newUpdateCommand(dockerCLI),
newWaitCommand(dockerCLI),
newListCommand(dockerCLI),
newInspectCommand(dockerCLI),
newPruneCommand(dockerCLI),
NewAttachCommand(dockerCli),
NewCommitCommand(dockerCli),
NewCopyCommand(dockerCli),
NewCreateCommand(dockerCli),
NewDiffCommand(dockerCli),
NewExecCommand(dockerCli),
NewExportCommand(dockerCli),
NewKillCommand(dockerCli),
NewLogsCommand(dockerCli),
NewPauseCommand(dockerCli),
NewPortCommand(dockerCli),
NewRenameCommand(dockerCli),
NewRestartCommand(dockerCli),
NewRmCommand(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,14 +2,13 @@ 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/moby/moby/client"
"github.com/docker/docker/api/types/container"
"github.com/spf13/cobra"
)
@ -18,14 +17,13 @@ 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{
@ -37,29 +35,18 @@ func newCommitCommand(dockerCLI command.Cli) *cobra.Command {
if len(args) > 1 {
options.reference = args[1]
}
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)
return runCommit(cmd.Context(), dockerCli, &options)
},
Annotations: map[string]string{
"aliases": "docker container commit, docker commit",
},
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
DisableFlagsInUseLine: true,
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
}
flags := cmd.Flags()
flags.SetInterspersed(false)
// 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.BoolVarP(&options.pause, "pause", "p", true, "Pause 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>")`)
@ -70,17 +57,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, client.ContainerCommitOptions{
response, err := dockerCli.Client().ContainerCommit(ctx, options.container, container.CommitOptions{
Reference: options.reference,
Comment: options.comment,
Author: options.author,
Changes: options.changes.GetSlice(),
NoPause: options.noPause,
Changes: options.changes.GetAll(),
Pause: options.pause,
})
if err != nil {
return err
}
_, _ = fmt.Fprintln(dockerCli.Out(), response.ID)
fmt.Fprintln(dockerCli.Out(), response.ID)
return nil
}

View File

@ -1,62 +0,0 @@
package container
import (
"context"
"errors"
"io"
"testing"
"github.com/docker/cli/internal/test"
"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 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.NoPause, true))
assert.Check(t, is.Equal(ctr, "container-id"))
return client.ContainerCommitResult{ID: "image-id"}, nil
},
})
cmd := newCommitCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs(
[]string{
"--author", "Author Name <author@name.com>",
"--change", "EXPOSE 80",
"--message", "commit message",
"--no-pause",
"container-id",
},
)
err := cmd.Execute()
assert.NilError(t, err)
assert.Assert(t, is.Equal(cli.OutBuffer().String(), "image-id\n"))
}
func TestRunCommitClientError(t *testing.T) {
clientError := errors.New("client error")
cli := test.NewFakeCli(&fakeClient{
containerCommitFunc: func(ctx context.Context, ctr string, options client.ContainerCommitOptions) (client.ContainerCommitResult, error) {
return client.ContainerCommitResult{}, clientError
},
})
cmd := newCommitCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs([]string{"container-id"})
err := cmd.Execute()
assert.ErrorIs(t, err, clientError)
}

View File

@ -1,5 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.22
// +build go1.22
package container
@ -8,8 +9,7 @@ import (
"sync"
"github.com/docker/cli/cli/command/completion"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/container"
"github.com/moby/sys/capability"
"github.com/moby/sys/signal"
"github.com/spf13/cobra"
@ -57,7 +57,7 @@ var logDriverOptions = map[string][]string{
"fluentd": {
"max-buffer-size", "mode", "env", "env-regex", "labels", "fluentd-address", "fluentd-async",
"fluentd-buffer-limit", "fluentd-request-ack", "fluentd-retry-wait", "fluentd-max-retries",
"fluentd-sub-second-precision", "fluentd-write-timeout", "tag",
"fluentd-sub-second-precision", "tag",
},
"gcplogs": {
"max-buffer-size", "mode", "env", "env-regex", "labels", "gcp-log-cmd", "gcp-meta-id", "gcp-meta-name",
@ -123,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)
@ -145,7 +145,7 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider)
}
// completeCgroupns implements shell completion for the `--cgroupns` option of `run` and `create`.
func completeCgroupns() cobra.CompletionFunc {
func completeCgroupns() completion.ValidArgsFn {
return completion.FromList(string(container.CgroupnsModeHost), string(container.CgroupnsModePrivate))
}
@ -156,7 +156,7 @@ func completeDetachKeys(_ *cobra.Command, _ []string, _ string) ([]string, cobra
// completeIpc implements shell completion for the `--ipc` option of `run` and `create`.
// The completion is partly composite.
func completeIpc(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
func completeIpc(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(toComplete) > 0 && strings.HasPrefix("container", toComplete) { //nolint:gocritic // not swapped, matches partly typed "container"
return []string{"container:"}, cobra.ShellCompDirectiveNoSpace
@ -176,7 +176,7 @@ func completeIpc(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
}
// completeLink implements shell completion for the `--link` option of `run` and `create`.
func completeLink(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
func completeLink(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return postfixWith(":", containerNames(dockerCLI, cmd, args, toComplete)), cobra.ShellCompDirectiveNoSpace
}
@ -185,13 +185,13 @@ func completeLink(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
// completeLogDriver implements shell completion for the `--log-driver` option of `run` and `create`.
// The log drivers are collected from a call to the Info endpoint with a fallback to a hard-coded list
// of the build-in log drivers.
func completeLogDriver(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
func completeLogDriver(dockerCLI completion.APIClientProvider) completion.ValidArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
res, err := dockerCLI.Client().Info(cmd.Context(), client.InfoOptions{})
info, err := dockerCLI.Client().Info(cmd.Context())
if err != nil {
return builtInLogDrivers(), cobra.ShellCompDirectiveNoFileComp
}
drivers := res.Info.Plugins.Log
drivers := info.Plugins.Log
return drivers, cobra.ShellCompDirectiveNoFileComp
}
}
@ -207,7 +207,7 @@ func completeLogOpt(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.S
}
// completePid implements shell completion for the `--pid` option of `run` and `create`.
func completePid(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
func completePid(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(toComplete) > 0 && strings.HasPrefix("container", toComplete) { //nolint:gocritic // not swapped, matches partly typed "container"
return []string{"container:"}, cobra.ShellCompDirectiveNoSpace
@ -278,14 +278,14 @@ 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 {
func completeVolumeDriver(dockerCLI completion.APIClientProvider) completion.ValidArgsFn {
return func(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
res, err := dockerCLI.Client().Info(cmd.Context(), client.InfoOptions{})
info, err := dockerCLI.Client().Info(cmd.Context())
if err != nil {
// fallback: the built-in drivers
return []string{"local"}, cobra.ShellCompDirectiveNoFileComp
}
drivers := res.Info.Plugins.Volume
drivers := info.Plugins.Volume
return drivers, cobra.ShellCompDirectiveNoFileComp
}
}

View File

@ -6,8 +6,8 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/moby/sys/signal"
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
@ -27,7 +27,7 @@ func TestCompleteLinuxCapabilityNames(t *testing.T) {
func TestCompletePid(t *testing.T) {
tests := []struct {
containerListFunc func(client.ContainerListOptions) (client.ContainerListResult, error)
containerListFunc func(container.ListOptions) ([]types.Container, error)
toComplete string
expectedCompletions []string
expectedDirective cobra.ShellCompDirective
@ -43,12 +43,10 @@ func TestCompletePid(t *testing.T) {
expectedDirective: cobra.ShellCompDirectiveNoSpace,
},
{
containerListFunc: func(client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1"),
*builders.Container("c2"),
},
containerListFunc: func(container.ListOptions) ([]types.Container, error) {
return []types.Container{
*builders.Container("c1"),
*builders.Container("c2"),
}, nil
},
toComplete: "container:",
@ -62,7 +60,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,7 +3,6 @@ package container
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
@ -16,10 +15,11 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/streams"
"github.com/docker/go-units"
"github.com/moby/go-archive"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/archive"
units "github.com/docker/go-units"
"github.com/morikuni/aec"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -121,20 +121,21 @@ 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{
Use: `cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH`,
Short: "Copy files/folders between a container and the local filesystem",
Long: `Copy files/folders between a container and the local filesystem
Use '-' as the source to read a tar archive from stdin
and extract it to a directory destination in a container.
Use '-' as the destination to stream a tar archive of a
container source to stdout.`,
Long: strings.Join([]string{
"Copy files/folders between a container and the local filesystem\n",
"\nUse '-' as the source to read a tar archive from stdin\n",
"and extract it to a directory destination in a container.\n",
"Use '-' as the destination to stream a tar archive of a\n",
"container source to stdout.",
}, ""),
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
if args[0] == "" {
@ -147,14 +148,13 @@ 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()
@ -202,15 +202,14 @@ func runCopy(ctx context.Context, dockerCli command.Cli, opts copyOptions) error
}
}
func resolveLocalPath(localPath string) (absPath string, _ error) {
absPath, err := filepath.Abs(localPath)
if err != nil {
return "", err
func resolveLocalPath(localPath string) (absPath string, err error) {
if absPath, err = filepath.Abs(localPath); err != nil {
return
}
return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
}
func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpConfig) (err error) {
func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpConfig) (err error) {
dstPath := copyConfig.destPath
srcPath := copyConfig.sourcePath
@ -226,17 +225,15 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
return err
}
apiClient := dockerCLI.Client()
client := dockerCli.Client()
// if client requests to follow symbol link, then must decide target file to be copied
var rebaseName string
if copyConfig.followLink {
src, err := apiClient.ContainerStatPath(ctx, copyConfig.container, client.ContainerStatPathOptions{
Path: srcPath,
})
srcStat, err := client.ContainerStatPath(ctx, copyConfig.container, srcPath)
// If the destination is a symbolic link, we should follow it.
if err == nil && src.Stat.Mode&os.ModeSymlink != 0 {
linkTarget := src.Stat.LinkTarget
if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
linkTarget := srcStat.LinkTarget
if !isAbs(linkTarget) {
// Join with the parent directory.
srcParent, _ := archive.SplitPathDirEntry(srcPath)
@ -251,24 +248,21 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
defer cancel()
cpRes, err := apiClient.CopyFromContainer(ctx, copyConfig.container, client.CopyFromContainerOptions{
SourcePath: srcPath,
})
content, stat, err := client.CopyFromContainer(ctx, copyConfig.container, srcPath)
if err != nil {
return err
}
content := cpRes.Content
defer func() { _ = content.Close() }()
defer content.Close()
if dstPath == "-" {
_, err = io.Copy(dockerCLI.Out(), content)
_, err = io.Copy(dockerCli.Out(), content)
return err
}
srcInfo := archive.CopyInfo{
Path: srcPath,
Exists: true,
IsDir: cpRes.Stat.Mode.IsDir(),
IsDir: stat.Mode.IsDir(),
RebaseName: rebaseName,
}
@ -290,12 +284,12 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
return archive.CopyTo(preArchive, srcInfo, dstPath)
}
restore, done := copyProgress(ctx, dockerCLI.Err(), copyFromContainerHeader, &copiedSize)
restore, done := copyProgress(ctx, dockerCli.Err(), copyFromContainerHeader, &copiedSize)
res := archive.CopyTo(preArchive, srcInfo, dstPath)
cancel()
<-done
restore()
_, _ = fmt.Fprintln(dockerCLI.Err(), "Successfully copied", progressHumanSize(copiedSize), "to", dstPath)
fmt.Fprintln(dockerCli.Err(), "Successfully copied", progressHumanSize(copiedSize), "to", dstPath)
return res
}
@ -304,50 +298,49 @@ 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) error {
func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpConfig) (err error) {
srcPath := copyConfig.sourcePath
dstPath := copyConfig.destPath
if srcPath != "-" {
// Get an absolute source path.
p, err := resolveLocalPath(srcPath)
srcPath, err = resolveLocalPath(srcPath)
if err != nil {
return err
}
srcPath = p
}
apiClient := dockerCLI.Client()
client := dockerCli.Client()
// Prepare destination copy info by stat-ing the container path.
dstInfo := archive.CopyInfo{Path: 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)
}
dstStat, err := client.ContainerStatPath(ctx, copyConfig.container, dstPath)
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()
// 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)
}
// 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)
dstInfo.Path = linkTarget
dstStat, err = client.ContainerStatPath(ctx, copyConfig.container, linkTarget)
}
// 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()
}
var (
@ -360,7 +353,7 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
content = os.Stdin
resolvedDstPath = dstInfo.Path
if !dstInfo.IsDir {
return fmt.Errorf(`destination "%s:%s" must be a directory`, copyConfig.container, dstPath)
return errors.Errorf("destination \"%s:%s\" must be a directory", copyConfig.container, dstPath)
}
} else {
// Prepare source copy info.
@ -403,27 +396,24 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
}
}
options := client.CopyToContainerOptions{
DestinationPath: resolvedDstPath,
Content: content,
CopyUIDGID: copyConfig.copyUIDGID,
options := container.CopyToContainerOptions{
AllowOverwriteDirWithFile: false,
CopyUIDGID: copyConfig.copyUIDGID,
}
if copyConfig.quiet {
_, err := apiClient.CopyToContainer(ctx, copyConfig.container, options)
return err
return client.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, options)
}
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
restore, done := copyProgress(ctx, dockerCLI.Err(), copyToContainerHeader, &copiedSize)
// TODO(thaJeztah): error-handling looks odd here; should it be handled differently?
_, err := apiClient.CopyToContainer(ctx, copyConfig.container, options)
restore, done := copyProgress(ctx, dockerCli.Err(), copyToContainerHeader, &copiedSize)
res := client.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, 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 err
return res
}
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be

View File

@ -9,12 +9,12 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/go-archive"
"github.com/moby/go-archive/compression"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/archive"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/fs"
"gotest.tools/v3/skip"
)
func TestRunCopyWithInvalidArguments(t *testing.T) {
@ -52,11 +52,9 @@ func TestRunCopyFromContainerToStdout(t *testing.T) {
tarContent := "the tar content"
cli := test.NewFakeCli(&fakeClient{
containerCopyFromFunc: func(ctr, srcPath string) (client.CopyFromContainerResult, error) {
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
assert.Check(t, is.Equal("container", ctr))
return client.CopyFromContainerResult{
Content: io.NopCloser(strings.NewReader(tarContent)),
}, nil
return io.NopCloser(strings.NewReader(tarContent)), container.PathStat{}, nil
},
})
err := runCopy(context.TODO(), cli, copyOptions{
@ -75,12 +73,10 @@ func TestRunCopyFromContainerToFilesystem(t *testing.T) {
destDir := fs.NewDir(t, "cp-test")
cli := test.NewFakeCli(&fakeClient{
containerCopyFromFunc: func(ctr, srcPath string) (client.CopyFromContainerResult, error) {
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
assert.Check(t, is.Equal("container", ctr))
readCloser, err := archive.Tar(srcDir.Path(), compression.None)
return client.CopyFromContainerResult{
Content: readCloser,
}, err
readCloser, err := archive.Tar(srcDir.Path(), archive.Uncompressed)
return readCloser, container.PathStat{}, err
},
})
err := runCopy(context.TODO(), cli, copyOptions{
@ -103,12 +99,10 @@ func TestRunCopyFromContainerToFilesystemMissingDestinationDirectory(t *testing.
defer destDir.Remove()
cli := test.NewFakeCli(&fakeClient{
containerCopyFromFunc: func(ctr, srcPath string) (client.CopyFromContainerResult, error) {
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
assert.Check(t, is.Equal("container", ctr))
readCloser, err := archive.TarWithOptions(destDir.Path(), &archive.TarOptions{})
return client.CopyFromContainerResult{
Content: readCloser,
}, err
return readCloser, container.PathStat{}, err
},
})
err := runCopy(context.TODO(), cli, copyOptions{
@ -158,7 +152,7 @@ func TestSplitCpArg(t *testing.T) {
}{
{
doc: "absolute path with colon",
os: "unix",
os: "linux",
path: "/abs/path:withcolon",
expectedPath: "/abs/path:withcolon",
},
@ -186,13 +180,9 @@ func TestSplitCpArg(t *testing.T) {
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
if tc.os == "windows" && runtime.GOOS != "windows" {
t.Skip("skipping windows test on non-windows platform")
}
if tc.os == "unix" && runtime.GOOS == "windows" {
t.Skip("skipping unix test on windows")
}
skip.If(t, tc.os == "windows" && runtime.GOOS != "windows" || tc.os == "linux" && runtime.GOOS == "windows")
ctr, path := splitCpArg(tc.path)
assert.Check(t, is.Equal(tc.expectedContainer, ctr))

View File

@ -1,30 +1,27 @@
package container
import (
"archive/tar"
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"path"
"strings"
"regexp"
"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/config/configfile"
"github.com/docker/cli/cli/config/types"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/internal/jsonstream"
"github.com/docker/cli/opts"
"github.com/moby/moby/api/types/mount"
"github.com/moby/moby/client"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/docker/docker/api/types/container"
imagetypes "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/jsonmessage"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@ -37,15 +34,15 @@ const (
)
type createOptions struct {
name string
platform string
pull string // always, missing, never
quiet bool
useAPISocket bool
name string
platform string
untrusted bool
pull string // always, missing, never
quiet 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
@ -58,13 +55,12 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
if len(args) > 1 {
copts.Args = args[1:]
}
return runCreate(cmd.Context(), dockerCLI, cmd.Flags(), &options, copts)
return runCreate(cmd.Context(), dockerCli, cmd.Flags(), &options, copts)
},
Annotations: map[string]string{
"aliases": "docker container create, docker create",
},
ValidArgsFunction: completion.ImageNames(dockerCLI, -1),
DisableFlagsInUseLine: true,
ValidArgsFunction: completion.ImageNames(dockerCli),
}
flags := cmd.Flags()
@ -73,37 +69,34 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
flags.StringVar(&options.name, "name", "", "Assign a name to the container")
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) // 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")
// 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")
command.AddPlatformFlag(flags, &options.platform)
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
copts = addFlags(flags)
addCompletions(cmd, dockerCLI)
addCompletions(cmd, dockerCli)
flags.VisitAll(func(flag *pflag.Flag) {
// Set a default completion function if none was set. We don't look
// 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)
})
return cmd
}
func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error {
if err := validatePullOpt(options.pull); err != nil {
return cli.StatusError{
Status: withHelp(err, "create").Error(),
StatusCode: 125,
}
reportError(dockerCli.Err(), "create", err.Error(), true)
return cli.StatusError{StatusCode: 125}
}
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
newEnv := make([]string, 0, len(proxyConfig))
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetAll()))
newEnv := []string{}
for k, v := range proxyConfig {
if v == nil {
newEnv = append(newEnv, k)
@ -114,10 +107,12 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
copts.env = *opts.NewListOptsRef(&newEnv, nil)
containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
if err != nil {
return cli.StatusError{
Status: withHelp(err, "create").Error(),
StatusCode: 125,
}
reportError(dockerCli.Err(), "create", err.Error(), true)
return cli.StatusError{StatusCode: 125}
}
if err = validateAPIVersion(containerCfg, dockerCli.Client().ClientVersion()); err != nil {
reportError(dockerCli.Err(), "create", err.Error(), true)
return cli.StatusError{StatusCode: 125}
}
id, err := createContainer(ctx, dockerCli, containerCfg, options)
if err != nil {
@ -134,27 +129,20 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
return err
}
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{
responseBody, err := dockerCli.Client().ImageCreate(ctx, img, imagetypes.CreateOptions{
RegistryAuth: encodedAuth,
Platforms: ociPlatforms,
Platform: options.platform,
})
if err != nil {
return err
}
defer func() {
_ = resp.Close()
}()
defer responseBody.Close()
out := dockerCli.Err()
if options.quiet {
out = streams.NewOut(io.Discard)
}
return jsonstream.Display(ctx, resp, out)
return jsonmessage.DisplayJSONMessagesToStream(responseBody, out, nil)
}
type cidFile struct {
@ -167,13 +155,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 fmt.Errorf("failed to remove the CID file '%s': %w", cid.path, err)
return errors.Wrapf(err, "failed to remove the CID file '%s'", cid.path)
}
return nil
@ -184,26 +172,26 @@ func (cid *cidFile) Write(id string) error {
return nil
}
if _, err := cid.file.Write([]byte(id)); err != nil {
return fmt.Errorf("failed to write the container ID to the file: %w", err)
return errors.Wrap(err, "failed to write the container ID to the file")
}
cid.written = true
return nil
}
func newCIDFile(cidPath string) (*cidFile, error) {
if cidPath == "" {
func newCIDFile(path string) (*cidFile, error) {
if path == "" {
return &cidFile{}, nil
}
if _, err := os.Stat(cidPath); err == nil {
return nil, errors.New("container ID file found, make sure the other container isn't running or delete " + cidPath)
if _, err := os.Stat(path); err == nil {
return nil, errors.Errorf("container ID file found, make sure the other container isn't running or delete %s", path)
}
f, err := os.Create(cidPath)
f, err := os.Create(path)
if err != nil {
return nil, fmt.Errorf("failed to create the container ID file: %w", err)
return nil, errors.Wrap(err, "failed to create the container ID file")
}
return &cidFile{path: cidPath, file: f}, nil
return &cidFile{path: path, file: f}, nil
}
//nolint:gocyclo
@ -212,23 +200,19 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
hostConfig := containerCfg.HostConfig
networkingConfig := containerCfg.NetworkingConfig
var namedRef reference.Named
warnOnOomKillDisable(*hostConfig, dockerCli.Err())
warnOnLocalhostDNS(*hostConfig, dockerCli.Err())
// TODO(thaJeztah): add a platform option-type / flag-type.
if options.platform != "" {
_, err = platforms.Parse(options.platform)
if err != nil {
return "", err
}
}
var (
trustedRef reference.Canonical
namedRef reference.Named
)
containerIDFile, err := newCIDFile(hostConfig.ContainerIDFile)
if err != nil {
return "", err
}
defer func() {
_ = containerIDFile.Close()
}()
defer containerIDFile.Close()
ref, err := reference.ParseAnyReference(config.Image)
if err != nil {
@ -236,91 +220,40 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
}
if named, ok := ref.(reference.Named); ok {
namedRef = reference.TagNameOnly(named)
}
const dockerConfigPathInContainer = "/run/secrets/docker/config.json"
var apiSocketCreds map[string]types.AuthConfig
if options.useAPISocket {
// We'll create two new mounts to handle this flag:
// 1. Mount the actual docker socket.
// 2. A synthezised ~/.docker/config.json with resolved tokens.
if dockerCli.ServerInfo().OSType == "windows" {
return "", errors.New("flag --use-api-socket can't be used with a Windows Docker Engine")
}
// 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: "/var/run/docker.sock",
Target: "/var/run/docker.sock",
BindOptions: &mount.BindOptions{},
})
/*
Ideally, we'd like to copy the config into a tmpfs but unfortunately,
the mounts won't be in place until we start the container. This can
leave around the config if the container doesn't get deleted.
We are using the most compose-secret-compatible approach,
which is implemented at
https://github.com/docker/compose/blob/main/pkg/compose/convergence.go#L737
// Prepare a tmpfs mount for our credentials so they go away after the
// container exits. We'll copy into this mount after the container is
// created.
containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
Type: mount.TypeTmpfs,
Target: "/docker/",
TmpfsOptions: &mount.TmpfsOptions{
SizeBytes: 1 << 20, // only need a small partition
Mode: 0o600,
},
})
*/
var envvarPresent bool
for _, envvar := range containerCfg.Config.Env {
if strings.HasPrefix(envvar, "DOCKER_CONFIG=") {
envvarPresent = true
}
}
// If the DOCKER_CONFIG env var is already present, we assume the client knows
// what they're doing and don't inject the creds.
if !envvarPresent {
// Resolve this here for later, ensuring we error our before we create the container.
creds, err := readCredentials(dockerCli)
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && !options.untrusted {
var err error
trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef)
if err != nil {
return "", fmt.Errorf("resolving credentials failed: %w", err)
}
if len(creds) > 0 {
// Set our special little location for the config file.
containerCfg.Config.Env = append(containerCfg.Config.Env, "DOCKER_CONFIG="+path.Dir(dockerConfigPathInContainer))
apiSocketCreds = creds // inject these after container creation.
return "", err
}
config.Image = reference.FamiliarString(trustedRef)
}
}
var platform *ocispec.Platform
if options.platform != "" {
p, err := platforms.Parse(options.platform)
if err != nil {
return "", invalidParameter(fmt.Errorf("error parsing specified platform: %w", err))
}
platform = &p
}
pullAndTagImage := func() error {
if err := pullImage(ctx, dockerCli, config.Image, options); err != nil {
return err
}
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
return image.TagTrusted(ctx, dockerCli, trustedRef, taggedRef)
}
return nil
}
var platform *specs.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") {
p, err := platforms.Parse(options.platform)
if err != nil {
return "", errors.Wrap(errdefs.InvalidParameter(err), "error parsing specified platform")
}
platform = &p
}
if options.pull == PullImageAlways {
if err := pullAndTagImage(); err != nil {
return "", err
@ -329,20 +262,13 @@ 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, client.ContainerCreateOptions{
Name: options.name,
// Image: config.Image, // TODO(thaJeztah): pass image-ref separate
Platform: platform,
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
})
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name)
if err != nil {
// Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior.
if 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))
fmt.Fprintf(dockerCli.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
}
if err := pullAndTagImage(); err != nil {
@ -350,14 +276,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
}
var retryErr error
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,
})
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name)
if retryErr != nil {
return "", retryErr
}
@ -366,24 +285,40 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
}
}
containerID = response.ID
for _, w := range response.Warnings {
_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
_, _ = fmt.Fprintf(dockerCli.Err(), "WARNING: %s\n", w)
}
err = containerIDFile.Write(containerID)
err = containerIDFile.Write(response.ID)
return response.ID, err
}
if options.useAPISocket && len(apiSocketCreds) > 0 {
// Create a new config file with just the auth.
newConfig := &configfile.ConfigFile{
AuthConfigs: apiSocketCreds,
}
func warnOnOomKillDisable(hostConfig container.HostConfig, stderr io.Writer) {
if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
fmt.Fprintln(stderr, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.")
}
}
if err := copyDockerConfigIntoContainer(ctx, dockerCli.Client(), containerID, dockerConfigPathInContainer, newConfig); err != nil {
return "", fmt.Errorf("injecting docker config.json into container failed: %w", 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
func warnOnLocalhostDNS(hostConfig container.HostConfig, stderr io.Writer) {
for _, dnsIP := range hostConfig.DNS {
if isLocalhost(dnsIP) {
fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
return
}
}
}
return containerID, err
// IPLocalhost is a regex pattern for IPv4 or IPv6 loopback range.
const ipLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)`
var localhostIPRegexp = regexp.MustCompile(ipLocalhost)
// IsLocalhost returns true if ip matches the localhost IP regular expression.
// Used for determining if nameserver settings are being passed which are
// localhost addresses
func isLocalhost(ip string) bool {
return localhostIPRegexp.MatchString(ip)
}
func validatePullOpt(val string) error {
@ -401,42 +336,3 @@ func validatePullOpt(val string) error {
)
}
}
// copyDockerConfigIntoContainer takes the client configuration and copies it
// into the container.
//
// The path should be an absolute path in the container, commonly
// /root/.docker/config.json.
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)
}
// We don't need to get super fancy with the tar creation.
var tarBuf bytes.Buffer
tarWriter := tar.NewWriter(&tarBuf)
_ = tarWriter.WriteHeader(&tar.Header{
Name: configPath,
Size: int64(configBuf.Len()),
Mode: 0o600,
})
if _, err := io.Copy(tarWriter, &configBuf); err != nil {
return fmt.Errorf("writing config to tar file for config copy: %w", err)
}
if err := tarWriter.Close(); err != nil {
return fmt.Errorf("closing tar for config copy failed: %w", err)
}
_, err := apiClient.CopyToContainer(ctx, containerID, client.CopyToContainerOptions{
DestinationPath: "/",
Content: &tarBuf,
})
if err != nil {
return fmt.Errorf("copying config.json into container failed: %w", err)
}
return nil
}

View File

@ -13,10 +13,13 @@ 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"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/pflag"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -110,34 +113,40 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.PullPolicy, func(t *testing.T) {
pullCounter := 0
apiClient := &fakeClient{
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
client := &fakeClient{
createContainerFunc: func(
config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *specs.Platform,
containerName string,
) (container.CreateResponse, error) {
defer func() { tc.ResponseCounter++ }()
switch tc.ResponseCounter {
case 0:
return client.ContainerCreateResult{}, fakeNotFound{}
return container.CreateResponse{}, fakeNotFound{}
default:
return client.ContainerCreateResult{ID: containerID}, nil
return container.CreateResponse{ID: containerID}, nil
}
},
imagePullFunc: func(ctx context.Context, parentReference string, options client.ImagePullOptions) (client.ImagePullResponse, error) {
imageCreateFunc: func(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
defer func() { pullCounter++ }()
return fakeStreamResult{ReadCloser: io.NopCloser(strings.NewReader(""))}, nil
return io.NopCloser(strings.NewReader("")), nil
},
infoFunc: func() (client.SystemInfoResult, error) {
return client.SystemInfoResult{
Info: system.Info{IndexServerAddress: "https://indexserver.example.com"},
}, nil
infoFunc: func() (system.Info, error) {
return system.Info{IndexServerAddress: "https://indexserver.example.com"}, nil
},
}
fakeCLI := test.NewFakeCli(apiClient)
fakeCLI := test.NewFakeCli(client)
id, err := createContainer(context.Background(), fakeCLI, config, &createOptions{
name: "name",
platform: runtime.GOOS,
pull: tc.PullPolicy,
name: "name",
platform: runtime.GOOS,
untrusted: true,
pull: tc.PullPolicy,
})
if tc.ExpectedErrMsg != "" {
@ -167,6 +176,7 @@ func TestCreateContainerImagePullPolicyInvalid(t *testing.T) {
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.PullPolicy, func(t *testing.T) {
dockerCli := test.NewFakeCli(&fakeClient{})
err := runCreate(
@ -179,78 +189,121 @@ func TestCreateContainerImagePullPolicyInvalid(t *testing.T) {
statusErr := cli.StatusError{}
assert.Check(t, errors.As(err, &statusErr))
assert.Check(t, is.Equal(statusErr.StatusCode, 125))
assert.Check(t, is.ErrorContains(err, tc.ExpectedErrMsg))
assert.Equal(t, statusErr.StatusCode, 125)
assert.Check(t, is.Contains(dockerCli.ErrBuffer().String(), tc.ExpectedErrMsg))
})
}
}
func TestCreateContainerValidateFlags(t *testing.T) {
for _, tc := range []struct {
name string
args []string
expectedErr string
func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
notaryFunc test.NotaryClientFuncType
}{
{
name: "with invalid --attach value",
args: []string{"--attach", "STDINFO", "myimage"},
expectedErr: `invalid argument "STDINFO" for "-a, --attach" flag: valid streams are STDIN, STDOUT and STDERR`,
name: "offline-notary-server",
notaryFunc: notary.GetOfflineNotaryRepository,
expectedError: "client is offline",
args: []string{"image:tag"},
},
} {
t.Run(tc.name, func(t *testing.T) {
cmd := newCreateCommand(test.NewFakeCli(&fakeClient{}))
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
if tc.expectedErr != "" {
assert.Check(t, is.ErrorContains(err, tc.expectedErr))
} else {
assert.Check(t, is.Nil(err))
}
})
{
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 {
tc := tc
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *specs.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
args []string
warnings []string
warning bool
name string
args []string
warning bool
}{
{
name: "container-create-no-warnings",
name: "container-create-without-oom-kill-disable",
args: []string{"image:tag"},
},
{
name: "container-create-daemon-single-warning",
args: []string{"image:tag"},
warnings: []string{"warning from daemon"},
name: "container-create-oom-kill-disable-false",
args: []string{"--oom-kill-disable=false", "image:tag"},
},
{
name: "container-create-daemon-multiple-warnings",
args: []string{"image:tag"},
warnings: []string{"warning from daemon", "another warning from daemon"},
name: "container-create-oom-kill-without-memory-limit",
args: []string{"--oom-kill-disable", "image:tag"},
warning: true,
},
{
name: "container-create-oom-kill-true-without-memory-limit",
args: []string{"--oom-kill-disable=true", "image:tag"},
warning: true,
},
{
name: "container-create-oom-kill-true-with-memory-limit",
args: []string{"--oom-kill-disable=true", "--memory=100M", "image:tag"},
},
{
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 {
tc := tc
t.Run(tc.name, func(t *testing.T) {
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
return client.ContainerCreateResult{Warnings: tc.warnings}, nil
cli := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *specs.Platform,
containerName string,
) (container.CreateResponse, error) {
return container.CreateResponse{}, nil
},
})
cmd := newCreateCommand(fakeCLI)
cmd := NewCreateCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NilError(t, err)
if tc.warning || len(tc.warnings) > 0 {
golden.Assert(t, fakeCLI.ErrBuffer().String(), tc.name+".golden")
if tc.warning {
golden.Assert(t, cli.ErrBuffer().String(), tc.name+".golden")
} else {
assert.Equal(t, fakeCLI.ErrBuffer().String(), "")
assert.Equal(t, cli.ErrBuffer().String(), "")
}
})
}
@ -272,10 +325,15 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
sort.Strings(expected)
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
sort.Strings(options.Config.Env)
assert.DeepEqual(t, options.Config.Env, expected)
return client.ContainerCreateResult{}, nil
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *specs.Platform,
containerName string,
) (container.CreateResponse, error) {
sort.Strings(config.Env)
assert.DeepEqual(t, config.Env, expected)
return container.CreateResponse{}, nil
},
})
fakeCLI.SetConfigFile(&configfile.ConfigFile{
@ -289,7 +347,7 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
},
},
})
cmd := newCreateCommand(fakeCLI)
cmd := NewCreateCommand(fakeCLI)
cmd.SetOut(io.Discard)
cmd.SetArgs([]string{"image:tag"})
err := cmd.Execute()
@ -298,5 +356,5 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
type fakeNotFound struct{}
func (fakeNotFound) NotFound() {}
func (fakeNotFound) Error() string { return "error fake not found" }
func (f fakeNotFound) NotFound() {}
func (f fakeNotFound) Error() string { return "error fake not found" }

View File

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

View File

@ -1,76 +0,0 @@
package container
import (
"context"
"errors"
"io"
"strings"
"testing"
"github.com/docker/cli/internal/test"
"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) (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.SetOut(io.Discard)
cmd.SetArgs([]string{"container-id"})
err := cmd.Execute()
assert.NilError(t, err)
diff := strings.SplitN(cli.OutBuffer().String(), "\n", 3)
assert.Assert(t, is.Len(diff, 3))
file0 := strings.TrimSpace(diff[0])
file1 := strings.TrimSpace(diff[1])
file2 := strings.TrimSpace(diff[2])
assert.Check(t, is.Equal(file0, "C /path/to/file0"))
assert.Check(t, is.Equal(file1, "A /path/to/file1"))
assert.Check(t, is.Equal(file2, "D /path/to/file2"))
}
func TestRunDiffClientError(t *testing.T) {
clientError := errors.New("client error")
cli := test.NewFakeCli(&fakeClient{
containerDiffFunc: func(ctx context.Context, containerID string) (client.ContainerDiffResult, error) {
return client.ContainerDiffResult{}, clientError
},
})
cmd := newDiffCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs([]string{"container-id"})
err := cmd.Execute()
assert.ErrorIs(t, err, clientError)
}

View File

@ -1,31 +0,0 @@
package container
import "github.com/containerd/errdefs"
func invalidParameter(err error) error {
if err == nil || errdefs.IsInvalidArgument(err) {
return err
}
return invalidParameterErr{err}
}
type invalidParameterErr struct{ error }
func (invalidParameterErr) InvalidParameter() {}
func (e invalidParameterErr) Unwrap() error {
return e.error
}
func notFound(err error) error {
if err == nil || errdefs.IsNotFound(err) {
return err
}
return notFoundErr{err}
}
type notFoundErr struct{ error }
func (notFoundErr) NotFound() {}
func (e notFoundErr) Unwrap() error {
return e.error
}

View File

@ -2,7 +2,6 @@ package container
import (
"context"
"errors"
"fmt"
"io"
@ -11,8 +10,10 @@ import (
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/opts"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@ -39,8 +40,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,16 +51,15 @@ 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 {
return ctr.State != container.StatePaused
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(ctr types.Container) bool {
return ctr.State != "paused"
}),
Annotations: map[string]string{
"category-top": "2",
"aliases": "docker container exec, docker exec",
},
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
@ -72,43 +72,43 @@ 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
}
// RunExec executes an `exec` command
func RunExec(ctx context.Context, dockerCLI command.Cli, containerIDorName string, options ExecOptions) error {
execOptions, err := parseExec(options, dockerCLI.ConfigFile())
func RunExec(ctx context.Context, dockerCli command.Cli, containerIDorName string, options ExecOptions) error {
execOptions, err := parseExec(options, dockerCli.ConfigFile())
if err != nil {
return err
}
apiClient := dockerCLI.Client()
apiClient := dockerCli.Client()
// We need to check the tty _before_ we do the ContainerExecCreate, because
// 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, client.ContainerInspectOptions{}); err != nil {
if _, err := apiClient.ContainerInspect(ctx, containerIDorName); err != nil {
return err
}
if !options.Detach {
if err := dockerCLI.In().CheckTty(execOptions.AttachStdin, execOptions.TTY); err != nil {
if !execOptions.Detach {
if err := dockerCli.In().CheckTty(execOptions.AttachStdin, execOptions.Tty); err != nil {
return err
}
}
fillConsoleSize(execOptions, dockerCLI)
fillConsoleSize(execOptions, dockerCli)
response, err := apiClient.ExecCreate(ctx, containerIDorName, *execOptions)
response, err := apiClient.ContainerExecCreate(ctx, containerIDorName, *execOptions)
if err != nil {
return err
}
@ -118,25 +118,24 @@ func RunExec(ctx context.Context, dockerCLI command.Cli, containerIDorName strin
return errors.New("exec ID empty")
}
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},
if execOptions.Detach {
return apiClient.ContainerExecStart(ctx, execID, container.ExecStartOptions{
Detach: execOptions.Detach,
Tty: execOptions.Tty,
ConsoleSize: execOptions.ConsoleSize,
})
return err
}
return interactiveExec(ctx, dockerCLI, execOptions, execID)
return interactiveExec(ctx, dockerCli, execOptions, execID)
}
func fillConsoleSize(execOptions *client.ExecCreateOptions, dockerCli command.Cli) {
if execOptions.TTY {
func fillConsoleSize(execOptions *container.ExecOptions, dockerCli command.Cli) {
if execOptions.Tty {
height, width := dockerCli.Out().GetTtySize()
execOptions.ConsoleSize = client.ConsoleSize{Height: height, Width: width}
execOptions.ConsoleSize = &[2]uint{height, width}
}
}
func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *client.ExecCreateOptions, execID string) error {
func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *container.ExecOptions, execID string) error {
// Interactive exec requested.
var (
out, stderr io.Writer
@ -150,7 +149,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *cl
out = dockerCli.Out()
}
if execOptions.AttachStderr {
if execOptions.TTY {
if execOptions.Tty {
stderr = dockerCli.Out()
} else {
stderr = dockerCli.Err()
@ -159,9 +158,9 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *cl
fillConsoleSize(execOptions, dockerCli)
apiClient := dockerCli.Client()
resp, err := apiClient.ExecAttach(ctx, execID, client.ExecAttachOptions{
TTY: execOptions.TTY,
ConsoleSize: client.ConsoleSize{Height: execOptions.ConsoleSize.Height, Width: execOptions.ConsoleSize.Width},
resp, err := apiClient.ContainerExecAttach(ctx, execID, container.ExecAttachOptions{
Tty: execOptions.Tty,
ConsoleSize: execOptions.ConsoleSize,
})
if err != nil {
return err
@ -178,8 +177,8 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *cl
inputStream: in,
outputStream: out,
errorStream: stderr,
resp: resp.HijackedResponse,
tty: execOptions.TTY,
resp: resp,
tty: execOptions.Tty,
detachKeys: execOptions.DetachKeys,
}
@ -187,7 +186,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *cl
}()
}()
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)
}
@ -201,8 +200,8 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *cl
return getExecExitStatus(ctx, apiClient, execID)
}
func getExecExitStatus(ctx context.Context, apiClient client.ExecAPIClient, execID string) error {
resp, err := apiClient.ExecInspect(ctx, execID, client.ExecInspectOptions{})
func getExecExitStatus(ctx context.Context, apiClient client.ContainerAPIClient, execID string) error {
resp, err := apiClient.ContainerExecInspect(ctx, execID)
if err != nil {
// If we can't connect, then the daemon probably died.
if !client.IsErrConnectionFailed(err) {
@ -219,18 +218,19 @@ func getExecExitStatus(ctx context.Context, apiClient client.ExecAPIClient, exec
// parseExec parses the specified args for the specified command and generates
// an ExecConfig from it.
func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*client.ExecCreateOptions, error) {
execOptions := &client.ExecCreateOptions{
func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*container.ExecOptions, error) {
execOptions := &container.ExecOptions{
User: execOpts.User,
Privileged: execOpts.Privileged,
TTY: execOpts.TTY,
Tty: execOpts.TTY,
Cmd: execOpts.Command,
Detach: execOpts.Detach,
WorkingDir: execOpts.Workdir,
}
// collect all the environment variables for the container
var err error
if execOptions.Env, err = opts.ReadKVEnvStrings(execOpts.EnvFile.GetSlice(), execOpts.Env.GetSlice()); err != nil {
if execOptions.Env, err = opts.ReadKVEnvStrings(execOpts.EnvFile.GetAll(), execOpts.Env.GetAll()); err != nil {
return nil, err
}

View File

@ -2,17 +2,17 @@ package container
import (
"context"
"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/moby/moby/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/fs"
@ -38,10 +38,10 @@ TWO=2
testcases := []struct {
options ExecOptions
configFile configfile.ConfigFile
expected client.ExecCreateOptions
expected container.ExecOptions
}{
{
expected: client.ExecCreateOptions{
expected: container.ExecOptions{
Cmd: []string{"command"},
AttachStdout: true,
AttachStderr: true,
@ -49,7 +49,7 @@ TWO=2
options: withDefaultOpts(ExecOptions{}),
},
{
expected: client.ExecCreateOptions{
expected: container.ExecOptions{
Cmd: []string{"command1", "command2"},
AttachStdout: true,
AttachStderr: true,
@ -64,19 +64,20 @@ TWO=2
TTY: true,
User: "uid",
}),
expected: client.ExecCreateOptions{
expected: container.ExecOptions{
User: "uid",
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
TTY: true,
Tty: true,
Cmd: []string{"command"},
},
},
{
options: withDefaultOpts(ExecOptions{Detach: true}),
expected: client.ExecCreateOptions{
Cmd: []string{"command"},
expected: container.ExecOptions{
Detach: true,
Cmd: []string{"command"},
},
},
{
@ -85,17 +86,19 @@ TWO=2
Interactive: true,
Detach: true,
}),
expected: client.ExecCreateOptions{
TTY: true,
Cmd: []string{"command"},
expected: container.ExecOptions{
Detach: true,
Tty: true,
Cmd: []string{"command"},
},
},
{
options: withDefaultOpts(ExecOptions{Detach: true}),
configFile: configfile.ConfigFile{DetachKeys: "de"},
expected: client.ExecCreateOptions{
expected: container.ExecOptions{
Cmd: []string{"command"},
DetachKeys: "de",
Detach: true,
},
},
{
@ -104,13 +107,14 @@ TWO=2
DetachKeys: "ab",
}),
configFile: configfile.ConfigFile{DetachKeys: "de"},
expected: client.ExecCreateOptions{
expected: container.ExecOptions{
Cmd: []string{"command"},
DetachKeys: "ab",
Detach: true,
},
},
{
expected: client.ExecCreateOptions{
expected: container.ExecOptions{
Cmd: []string{"command"},
AttachStdout: true,
AttachStderr: true,
@ -118,12 +122,12 @@ TWO=2
},
options: func() ExecOptions {
o := withDefaultOpts(ExecOptions{})
_ = o.EnvFile.Set(tmpFile.Path())
o.EnvFile.Set(tmpFile.Path())
return o
}(),
},
{
expected: client.ExecCreateOptions{
expected: container.ExecOptions{
Cmd: []string{"command"},
AttachStdout: true,
AttachStderr: true,
@ -131,25 +135,23 @@ 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 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))
})
for _, testcase := range testcases {
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{})
assert.Check(t, execOpts.EnvFile.Set("no-such-env-file"))
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))
@ -176,8 +178,8 @@ func TestRunExec(t *testing.T) {
doc: "inspect error",
options: NewExecOptions(),
client: &fakeClient{
inspectFunc: func(string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{}, errors.New("failed inspect")
inspectFunc: func(string) (types.ContainerJSON, error) {
return types.ContainerJSON{}, errors.New("failed inspect")
},
},
expectedError: "failed inspect",
@ -194,7 +196,7 @@ func TestRunExec(t *testing.T) {
t.Run(testcase.doc, func(t *testing.T) {
fakeCLI := test.NewFakeCli(testcase.client)
err := RunExec(context.TODO(), fakeCLI, "the-container", testcase.options)
err := RunExec(context.TODO(), fakeCLI, "thecontainer", testcase.options)
if testcase.expectedError != "" {
assert.ErrorContains(t, err, testcase.expectedError)
} else if !assert.Check(t, err) {
@ -206,8 +208,8 @@ func TestRunExec(t *testing.T) {
}
}
func execCreateWithID(_ string, _ client.ExecCreateOptions) (client.ExecCreateResult, error) {
return client.ExecCreateResult{ID: "exec-id"}, nil
func execCreateWithID(_ string, _ container.ExecOptions) (types.IDResponse, error) {
return types.IDResponse{ID: "execid"}, nil
}
func TestGetExecExitStatus(t *testing.T) {
@ -234,13 +236,13 @@ func TestGetExecExitStatus(t *testing.T) {
}
for _, testcase := range testcases {
apiClient := &fakeClient{
execInspectFunc: func(id string) (client.ExecInspectResult, error) {
client := &fakeClient{
execInspectFunc: func(id string) (container.ExecInspect, error) {
assert.Check(t, is.Equal(execID, id))
return client.ExecInspectResult{ExitCode: testcase.exitCode}, testcase.inspectError
return container.ExecInspect{ExitCode: testcase.exitCode}, testcase.inspectError
},
}
err := getExecExitStatus(context.Background(), apiClient, execID)
err := getExecExitStatus(context.Background(), client, execID)
assert.Check(t, is.Equal(testcase.expectedError, err))
}
}
@ -250,20 +252,20 @@ func TestNewExecCommandErrors(t *testing.T) {
name string
args []string
expectedError string
containerInspectFunc func(img string) (client.ContainerInspectResult, error)
containerInspectFunc func(img string) (types.ContainerJSON, error)
}{
{
name: "client-error",
args: []string{"5cb5bb5e4a3b", "-t", "-i", "bash"},
expectedError: "something went wrong",
containerInspectFunc: func(containerID string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{}, errors.New("something went wrong")
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
return types.ContainerJSON{}, errors.Errorf("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,15 +2,12 @@ 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"
)
@ -19,8 +16,8 @@ type exportOptions struct {
output string
}
// newExportCommand creates a new "docker container export" command.
func newExportCommand(dockerCLI command.Cli) *cobra.Command {
// NewExportCommand creates a new `docker export` command
func NewExportCommand(dockerCli command.Cli) *cobra.Command {
var opts exportOptions
cmd := &cobra.Command{
@ -29,13 +26,12 @@ func newExportCommand(dockerCLI command.Cli) *cobra.Command {
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.container = args[0]
return runExport(cmd.Context(), dockerCLI, opts)
return runExport(cmd.Context(), dockerCli, opts)
},
Annotations: map[string]string{
"aliases": "docker container export, docker export",
},
ValidArgsFunction: completion.ContainerNames(dockerCLI, true),
DisableFlagsInUseLine: true,
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
}
flags := cmd.Flags()
@ -45,28 +41,27 @@ func newExportCommand(dockerCLI command.Cli) *cobra.Command {
return cmd
}
func runExport(ctx context.Context, dockerCLI command.Cli, opts exportOptions) error {
var output io.Writer
if opts.output == "" {
if dockerCLI.Out().IsTerminal() {
return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect")
}
output = dockerCLI.Out()
} else {
writer, err := atomicwriter.New(opts.output, 0o600)
if err != nil {
return fmt.Errorf("failed to export container: %w", err)
}
defer writer.Close()
output = writer
func runExport(ctx context.Context, dockerCli command.Cli, opts exportOptions) error {
if opts.output == "" && dockerCli.Out().IsTerminal() {
return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect")
}
responseBody, err := dockerCLI.Client().ContainerExport(ctx, opts.container, client.ContainerExportOptions{})
if err := command.ValidateOutputPath(opts.output); err != nil {
return errors.Wrap(err, "failed to export container")
}
clnt := dockerCli.Client()
responseBody, err := clnt.ContainerExport(ctx, opts.container)
if err != nil {
return err
}
defer responseBody.Close()
_, err = io.Copy(output, responseBody)
return err
if opts.output == "" {
_, err := io.Copy(dockerCli.Out(), responseBody)
return err
}
return command.CopyToFile(opts.output, responseBody)
}

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,12 +15,11 @@ func TestContainerExportOutputToFile(t *testing.T) {
defer dir.Remove()
cli := test.NewFakeCli(&fakeClient{
containerExportFunc: func(container string) (client.ContainerExportResult, error) {
// FIXME(thaJeztah): how to mock this?
return mockContainerExportResult("bar"), nil
containerExportFunc: func(container string) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("bar")), nil
},
})
cmd := newExportCommand(cli)
cmd := NewExportCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs([]string{"-o", dir.Join("foo"), "container"})
assert.NilError(t, cmd.Execute())
@ -34,16 +33,17 @@ func TestContainerExportOutputToFile(t *testing.T) {
func TestContainerExportOutputToIrregularFile(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerExportFunc: func(container string) (client.ContainerExportResult, error) {
// FIXME(thaJeztah): how to mock this?
return mockContainerExportResult("foo"), nil
containerExportFunc: func(container string) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("foo")), nil
},
})
cmd := newExportCommand(cli)
cmd := NewExportCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs([]string{"-o", "/dev/random", "container"})
const expected = `failed to export container: cannot write to a character device file`
assert.Error(t, cmd.Execute(), expected)
err := cmd.Execute()
assert.Assert(t, err != nil)
expected := `"/dev/random" must be a directory or a regular file`
assert.ErrorContains(t, err, expected)
}

View File

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

View File

@ -5,8 +5,7 @@ import (
"testing"
"github.com/docker/cli/cli/command/formatter"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types/container"
"gotest.tools/v3/assert"
)
@ -17,7 +16,7 @@ func TestDiffContextFormatWrite(t *testing.T) {
expected string
}{
{
formatter.Context{Format: newDiffFormat("table")},
formatter.Context{Format: NewDiffFormat("table")},
`CHANGE TYPE PATH
C /var/log/app.log
A /usr/app/app.js
@ -25,7 +24,7 @@ D /usr/app/old_app.js
`,
},
{
formatter.Context{Format: newDiffFormat("table {{.Path}}")},
formatter.Context{Format: NewDiffFormat("table {{.Path}}")},
`PATH
/var/log/app.log
/usr/app/app.js
@ -33,7 +32,7 @@ D /usr/app/old_app.js
`,
},
{
formatter.Context{Format: newDiffFormat("{{.Type}}: {{.Path}}")},
formatter.Context{Format: NewDiffFormat("{{.Type}}: {{.Path}}")},
`C: /var/log/app.log
A: /usr/app/app.js
D: /usr/app/old_app.js
@ -41,19 +40,18 @@ D: /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"},
},
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"},
}
for _, tc := range cases {
tc := tc
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,7 +5,8 @@ import (
"sync"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/go-units"
"github.com/docker/docker/pkg/stringid"
units "github.com/docker/go-units"
)
const (
@ -21,8 +22,6 @@ const (
winMemUseHeader = "PRIV WORKING SET" // Used only on Windows
memUseHeader = "MEM USAGE / LIMIT" // Used only on Linux
pidsHeader = "PIDS" // Used only on Linux
noValue = "--"
)
// StatsEntry represents the statistics data collected from a container
@ -167,23 +166,22 @@ 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:]
}
return noValue
return "--"
}
func (c *statsContext) ID() string {
if c.trunc {
return formatter.TruncateID(c.s.ID)
return stringid.TruncateID(c.s.ID)
}
return c.s.ID
}
func (c *statsContext) CPUPerc() string {
if c.s.IsInvalid {
return noValue
return "--"
}
return formatPercentage(c.s.CPUPercentage)
}
@ -200,28 +198,28 @@ func (c *statsContext) MemUsage() string {
func (c *statsContext) MemPerc() string {
if c.s.IsInvalid || c.os == winOSType {
return noValue
return "--"
}
return formatPercentage(c.s.MemoryPercentage)
}
func (c *statsContext) NetIO() string {
if c.s.IsInvalid {
return noValue
return "--"
}
return units.HumanSizeWithPrecision(c.s.NetworkRx, 3) + " / " + units.HumanSizeWithPrecision(c.s.NetworkTx, 3)
}
func (c *statsContext) BlockIO() string {
if c.s.IsInvalid {
return noValue
return "--"
}
return units.HumanSizeWithPrecision(c.s.BlockRead, 3) + " / " + units.HumanSizeWithPrecision(c.s.BlockWrite, 3)
}
func (c *statsContext) PIDs() string {
if c.s.IsInvalid || c.os == winOSType {
return noValue
return "--"
}
return strconv.FormatUint(c.s.PidsCurrent, 10)
}

View File

@ -5,181 +5,45 @@ 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) {
const actorID = "c74518277ddc15a6afeaaeb06ee5f7433dcb27188224777c1efa7df1e8766d65"
containerID := stringid.GenerateRandomID()
var ctx statsContext
tests := []struct {
name string
tt := []struct {
stats StatsEntry
osType string
expValue string
expHeader string
call func() string
}{
{
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,
},
{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},
}
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)
}
})
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)
}
}
}
@ -284,7 +148,7 @@ container2 -- --
`,
},
}
entries := []StatsEntry{
stats := []StatsEntry{
{
Container: "container1",
CPUPercentage: 20,
@ -314,10 +178,11 @@ container2 -- --
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out
err := statsFormatWrite(tc.context, entries, "windows", false)
err := statsFormatWrite(tc.context, stats, "windows", false)
if err != nil {
assert.Error(t, err, tc.expected)
} else {
@ -358,6 +223,7 @@ func TestContainerStatsContextWriteWithNoStats(t *testing.T) {
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
err := statsFormatWrite(tc.context, []StatsEntry{}, "linux", false)
assert.NilError(t, err)
@ -399,6 +265,7 @@ func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
err := statsFormatWrite(tc.context, []StatsEntry{}, "windows", false)
assert.NilError(t, err)
@ -409,46 +276,45 @@ func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
}
func TestContainerStatsContextWriteTrunc(t *testing.T) {
tests := []struct {
doc string
var out bytes.Buffer
contexts := []struct {
context formatter.Context
trunc bool
expected string
}{
{
doc: "non-truncated",
context: formatter.Context{
formatter.Context{
Format: "{{.ID}}",
Output: &out,
},
expected: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc\n",
false,
"b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc\n",
},
{
doc: "truncated",
context: formatter.Context{
formatter.Context{
Format: "{{.ID}}",
Output: &out,
},
trunc: true,
expected: "b95a83497c91\n",
true,
"b95a83497c91\n",
},
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out
err := statsFormatWrite(tc.context, []StatsEntry{{ID: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc"}}, "linux", tc.trunc)
assert.NilError(t, err)
assert.Check(t, is.Equal(tc.expected, out.String()))
})
for _, context := range contexts {
statsFormatWrite(context.context, []StatsEntry{{ID: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc"}}, "linux", context.trunc)
assert.Check(t, is.Equal(context.expected, out.String()))
// Clean buffer
out.Reset()
}
}
func BenchmarkStatsFormat(b *testing.B) {
b.ReportAllocs()
entries := genStats()
stats := genStats()
for i := 0; i < b.N; i++ {
for _, s := range entries {
for _, s := range stats {
_ = s.CPUPerc()
_ = s.MemUsage()
_ = s.MemPerc()
@ -471,9 +337,9 @@ func genStats() []statsContext {
NetworkTx: 987.654321,
PidsCurrent: 123456789,
}}
entries := make([]statsContext, 0, 100)
for range 100 {
entries = append(entries, entry)
stats := make([]statsContext, 100)
for i := 0; i < 100; i++ {
stats = append(stats, entry)
}
return entries
return stats
}

View File

@ -8,8 +8,9 @@ import (
"sync"
"github.com/docker/cli/cli/command"
"github.com/moby/moby/api/pkg/stdcopy"
"github.com/moby/moby/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/stdcopy"
"github.com/moby/term"
"github.com/sirupsen/logrus"
)
@ -18,18 +19,6 @@ 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 {
@ -38,7 +27,7 @@ type hijackedIOStreamer struct {
outputStream io.Writer
errorStream io.Writer
resp client.HijackedResponse
resp types.HijackedResponse
tty bool
detachKeys string
@ -95,9 +84,12 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
// Use sync.Once so we may call restore multiple times but ensure we
// only restore the terminal once.
restore = sync.OnceFunc(func() {
_ = restoreTerminal(h.streams, h.inputStream)
})
var restoreOnce sync.Once
restore = func() {
restoreOnce.Do(func() {
_ = restoreTerminal(h.streams, h.inputStream)
})
}
// Wrap the input to detect detach escape sequence.
// Use default escape keys if an invalid sequence is given.
@ -111,10 +103,7 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
}
}
h.inputStream = &readCloserWrapper{
Reader: term.NewEscapeProxy(h.inputStream, escapeKeys),
closer: h.inputStream.Close,
}
h.inputStream = ioutils.NewReadCloserWrapper(term.NewEscapeProxy(h.inputStream, escapeKeys), 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.24
//go:build go1.22
package container
@ -11,7 +11,6 @@ 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"
)
@ -22,7 +21,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{
@ -31,10 +30,9 @@ 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),
DisableFlagsInUseLine: true,
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
}
flags := cmd.Flags()
@ -44,13 +42,11 @@ func newInspectCommand(dockerCLI command.Cli) *cobra.Command {
return cmd
}
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) {
res, err := apiClient.ContainerInspect(ctx, ref, client.ContainerInspectOptions{Size: opts.size})
if err != nil {
return nil, nil, err
}
return &res.Container, res.Raw, nil
})
func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error {
client := dockerCli.Client()
getRefFunc := func(ref string) (any, []byte, error) {
return client.ContainerInspectWithRaw(ctx, ref, opts.size)
}
return inspect.Inspect(dockerCli.Out(), opts.refs, opts.format, getRefFunc)
}

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