Compare commits

..

59 Commits

Author SHA1 Message Date
3713ee1eea Merge pull request #4395 from thaJeztah/24.0_backport_fix-connhelper-docker-example
Some checks failed
build / prepare (push) Has been cancelled
build / build (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
e2e / e2e (19.03-dind, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, experimental) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, non-experimental) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, connhelper-ssh) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, experimental) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, non-experimental) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-11) (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
[24.0 backport] ssh: fix error on commandconn close, add ping and default timeout
2023-06-30 11:55:44 -06:00
2d5f041bde commandconn: return original error while closing
Changes the `Read` and `Write` error handling
logic to return the original error while closing
the connection. We still skip calling `handleEOF`
if already closing the connection.

Fixes the flaky `TestCloseWhileWriting` and
`TestCloseWhileReading` tests.

Co-authored-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit d5f564adaa)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-30 19:23:36 +02:00
520e3600ee commandconn: don't return error if command closed successfully
---
commandconn: fix race on `Close()`

During normal operation, if a `Read()` or `Write()` call results
in an EOF, we call `onEOF()` to handle the terminating command,
and store it's exit value.

However, if a Read/Write call was blocked while `Close()` is called
the in/out pipes are immediately closed which causes an EOF to be
returned. Here, we shouldn't call `onEOF()`, since the reason why
we got an EOF is because we're already terminating the connection.
This also prevents a race between two calls to the commands `Wait()`,
in the `Close()` call and `onEOF()`

---
Add CLI init timeout to SSH connections

---
connhelper: add 30s ssh default dialer timeout

(same as non-ssh dialer)

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
(cherry picked from commit a5ebe2282a)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-30 19:23:12 +02:00
fad718c7ea Merge pull request #4393 from thaJeztah/24.0_backport_debug_relax
[24.0 backport] docker info: fix condition for printing debug information
2023-06-30 15:38:59 +02:00
cd68c8f003 docker info: fix condition for printing debug information
The daemon collects this information regardless if "debug" is
enabled. Print the debugging information if either the daemon,
or the client has debug enabled.

We should probably improve this logic and print any of these if
set (but some special rules are needed for file-descriptors, which
may use "-1".

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 92d7a234dd)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-30 15:15:38 +02:00
05fabe63ba Merge pull request #4368 from thaJeztah/24.0_backport_update_buildx_0.11
[24.0 backport] Dockerfile: update gotestsum to v1.10.0, buildx v0.11.0
2023-06-28 06:22:26 -06:00
0a2dcdb446 Merge pull request #4381 from thaJeztah/24.0_backport_update-link-overlay-driver
[24.0 backport] docs: update link location for the overlay driver
2023-06-27 12:35:42 +02:00
a78fd6ca69 docs: update link location for the overlay driver
File location changes in docker/docs#17176

Signed-off-by: David Karlsson <david.karlsson@docker.com>
(cherry picked from commit 035e26fb0b)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-27 09:28:19 +02:00
ddb9220abf Merge pull request #4375 from dvdksn/24.0_backport_fix-staticip-example
[24.0 Backport] Fix static ip example (docker run)
2023-06-26 17:22:57 +02:00
9cd335d44b docs: fix static ip example, network needs a subnet
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 5936fd2a86)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2023-06-26 17:04:24 +02:00
bcc889f6cf Merge pull request #4373 from dvdksn/24.0_backport_dockerd-fix-alternative-runtimes-link
[24.0 Backport] Fix broken link in dockerd cli reference
2023-06-26 16:19:10 +02:00
d61e4fe879 docs: fix broken link
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit b85d6a8f9e)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2023-06-26 15:11:55 +02:00
ee62dcd8dc Merge pull request #4369 from thaJeztah/24.0_backport_dockerd-runtimes-refresh
[24.0 backport] docs: update the runtime configuration section
2023-06-26 14:27:36 +02:00
b3750a8461 Merge pull request #4371 from thaJeztah/24.0_backport_no_homedir
[24.0 backport] cli/command/context: don't use pkg/homedir in test
2023-06-26 06:12:01 -06:00
8e3a2942a5 cli/command/context: don't use pkg/homedir in test
I'm considering deprecating the "Key()" utility, as it was only
used in tests.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 79ff64f06d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:37:34 +02:00
c3ef1ceadf docs: update the runtime configuration section
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
(cherry picked from commit 6c7d17fa01)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 12:46:56 +02:00
44eebb8bc1 Dockerfile: update buildx to v0.11.0
Update the version of buildx we use in the dev-container to v0.11.0;
https://github.com/docker/buildx/releases/tag/v0.11.0

Full diff: https://github.com/docker/buildx/compare/v0.10.4..v0.11.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit bf5d1ce973)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 12:17:36 +02:00
7ecfa2e7fd Dockerfile: update gotestsum to v1.10.0
full diff: https://github.com/gotestyourself/gotestsum/compare/v1.8.2...v1.10.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9c2694d2b0)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 12:17:33 +02:00
751bb353fe Merge pull request #4351 from thaJeztah/24.0_backport_update_go_1.20.5
[24.0 backport] update go to go1.20.5, alpine 3.17
2023-06-21 10:55:21 +02:00
f11f309090 update go to go1.20.5
go1.20.5 (released 2023-06-06) includes four security fixes to the cmd/go and
runtime packages, as well as bug fixes to the compiler, the go command, the
runtime, and the crypto/rsa, net, and os packages. See the Go 1.20.5 milestone
on our issue tracker for details:

https://github.com/golang/go/issues?q=milestone%3AGo1.20.5+label%3ACherryPickApproved

full diff: https://github.com/golang/go/compare/go1.20.4...go1.20.5

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

- cmd/go: cgo code injection
  The go command may generate unexpected code at build time when using cgo. This
  may result in unexpected behavior when running a go program which uses cgo.

  This may occur when running an untrusted module which contains directories with
  newline characters in their names. Modules which are retrieved using the go command,
  i.e. via "go get", are not affected (modules retrieved using GOPATH-mode, i.e.
  GO111MODULE=off, may be affected).

  Thanks to Juho Nurminen of Mattermost for reporting this issue.

  This is CVE-2023-29402 and Go issue https://go.dev/issue/60167.

- runtime: unexpected behavior of setuid/setgid binaries

  The Go runtime didn't act any differently when a binary had the setuid/setgid
  bit set. On Unix platforms, if a setuid/setgid binary was executed with standard
  I/O file descriptors closed, opening any files could result in unexpected
  content being read/written with elevated prilieges. Similarly if a setuid/setgid
  program was terminated, either via panic or signal, it could leak the contents
  of its registers.

  Thanks to Vincent Dehors from Synacktiv for reporting this issue.

  This is CVE-2023-29403 and Go issue https://go.dev/issue/60272.

- cmd/go: improper sanitization of LDFLAGS

  The go command may execute arbitrary code at build time when using cgo. This may
  occur when running "go get" on a malicious module, or when running any other
  command which builds untrusted code. This is can by triggered by linker flags,
  specified via a "#cgo LDFLAGS" directive.

  Thanks to Juho Nurminen of Mattermost for reporting this issue.

  This is CVE-2023-29404 and CVE-2023-29405 and Go issues https://go.dev/issue/60305 and https://go.dev/issue/60306.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 3b8d5da66b)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-14 21:37:48 +02:00
3a6c11773d Dockerfile: update ALPINE_VERSION to 3.17
Official Golang images are now only available for 3.18 and 3.17;
3.18 doesn't look to play well with gotestsum, so sticking to
an older version.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit acb248f8d5)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-14 21:37:46 +02:00
0823df7daa Merge pull request #4339 from thaJeztah/24.0_backport_move_attach_keys
[24.0 backport] docs: move "--detach-keys" example to examples section, add to "docker run" as well
2023-06-12 21:37:14 +02:00
11af1189d7 docs: add "--detach-keys" example to docker run reference
This is a copy of the section we have on the "docker attach" reference page.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 47951ff446)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-09 10:07:19 +02:00
f118c05e87 docs: move "--detach-keys" example to examples section
Also adds a named anchor, so that the section gets linked from the
options table.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit c17b0df2a5)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-09 10:07:19 +02:00
be0e76bf84 Merge pull request #4326 from thaJeztah/24.0_backport_fix_context_godoc
[24.0 backport] cli/command: fix GoDoc referencing wrong const
2023-06-02 14:20:15 +02:00
f66f7ed7ff cli/command: fix GoDoc referencing wrong const
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 0692d762ac)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-02 14:13:14 +02:00
ec621aae2d Merge pull request #4328 from thaJeztah/24.0_backport_dockerfile_goproxy
[24.0 backport] Dockerfile.vendor: update GOPROXY to use default with fallback
2023-06-02 14:08:51 +02:00
2814c01b09 Dockerfile.vendor: update GOPROXY to use default with fallback
Use the default proxy, to assist with vanity domains mis-behaving, but keep
a fallback for situations where we need to get modules from GitHub directly.

This should hopefully help with the gopkg.in/yaml.v2 domain often going AWOL;

    #14 245.9 	gopkg.in/yaml.v2@v2.4.0: unrecognized import path "gopkg.in/yaml.v2": reading https://gopkg.in/yaml.v2?go-get=1: 502 Bad Gateway
    #14 245.9 	server response: Cannot obtain refs from GitHub: cannot talk to GitHub: Get https://github.com/go-yaml/yaml.git/info/refs?service=git-upload-pack: write tcp 10.131.9.188:60820->140.82.121.3:443: write: broken pipe

    curl 'https://gopkg.in/yaml.v2?go-get=1'
    Cannot obtain refs from GitHub: cannot talk to GitHub: Get https://github.com/go-yaml/yaml.git/info/refs?service=git-upload-pack: write tcp 10.131.9.188:60820->140.82.121.3:443: write: broken pipe

From the Go documentation; https://go.dev/ref/mod#goproxy-protocol

> List elements may be separated by commas (,) or pipes (|), which determine error
> fallback behavior. When a URL is followed by a comma, the go command falls back
> to later sources only after a 404 (Not Found) or 410 (Gone) response. When a URL
> is followed by a pipe, the go command falls back to later sources after any error,
> including non-HTTP errors such as timeouts. This error handling behavior lets a
> proxy act as a gatekeeper for unknown modules. For example, a proxy could respond
> with error 403 (Forbidden) for modules not on an approved list (see Private proxy
> serving private modules).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 6458dcbe51)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-02 13:15:18 +02:00
4dc5ea0e80 Merge pull request #4320 from thaJeztah/24.0_update_engine
[24.0] vendor: github.com/docker/docker v24.0.2
2023-06-01 14:38:31 +02:00
32f66cbe51 vendor: github.com/docker/docker v24.0.2
no changes in vendored files

full diff: https://github.com/docker/docker/compare/v24.0.1...v24.0.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-31 22:48:27 +02:00
cb74dfcd85 Merge pull request #4313 from thaJeztah/24.0_update_engine
Some checks failed
build / prepare (push) Has been cancelled
build / build (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
e2e / e2e (19.03-dind, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, experimental) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, non-experimental) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, connhelper-ssh) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, experimental) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, non-experimental) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-11) (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
[24.0] vendor: github.com/docker/docker v24.0.1
2023-05-25 22:26:27 +02:00
dc4707edb0 [24.0] vendor: github.com/docker/docker v24.0.1
no changes in vendored files

full diff: https://github.com/docker/docker/compare/v24.0.0-rc.3...v24.0.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-25 22:11:54 +02:00
680212238b Merge pull request #4310 from thaJeztah/24.0_backport_fix_daemon_proxy
Some checks failed
build / prepare (push) Has been cancelled
build / build (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
e2e / e2e (19.03-dind, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, experimental) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, non-experimental) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, connhelper-ssh) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, experimental) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, non-experimental) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-11) (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
2023-05-19 17:51:02 +02:00
298e67926e docs: fix example for proxies in daemon.json
commit c846428cb6 added proxies to the
example `daemon.json`, based on the implementation that was added in
427c7cc5f8.

However, a follow-up pull request changed the proxy-configuration in`daemon.json`
to nest the configuration in a "proxies" struct, and the documentation was
not updated accordingly; see:
101dafd049

This patch fixes the example.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 2713d0bcde)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-19 17:29:30 +02:00
aa40216965 Merge pull request #4308 from thaJeztah/24.0_backport_docs_fixes 2023-05-19 15:08:06 +02:00
9175ffa9b2 man: remove devicemapper from examples
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 4c11f73dcb)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-19 14:45:52 +02:00
beb0330a72 Correct "ps --no-trunc" example output
Signed-off-by: A. Lester Buck III <github-reg@nbolt.com>
(cherry picked from commit 988e37956d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-19 14:38:03 +02:00
405be90634 docs: remove AuFS from glossary
The AuFS storage driver was deprecated and now removed.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b222900520)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-19 14:37:51 +02:00
7a269817b5 docs: remove Docker Toolbox from glossary
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit e4211c91ed)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-19 14:37:51 +02:00
41ef7c45cc docs: remove boot2docker and docker-machine from glossary
boot2docker is deprecated, and so is docker-machine

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit c246ea8517)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-19 14:37:48 +02:00
199b872c98 Merge pull request #4302 from thaJeztah/24.0_backport_completion_remove_aufs_overlay
[24.0 backport] contrib/completion: remove aufs, legacy overlay
2023-05-19 10:07:01 +02:00
661f70b52d Merge pull request #4305 from thaJeztah/24.0_backport_daemon_remove_deprecated_drivers
[24.0 backport] docs: remove uses of deprecated AuFS, legacy overlay storage drivers
2023-05-19 09:50:27 +02:00
c184a61dab docs/deprecated: remove "disabled by default" for AuFS, overlay
These drivers have been removed in docker 24.0, so it's no longer
possible to enable them.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit c61b565183)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-19 01:23:37 +02:00
e7a60449f7 docs: remove aufs and legacy overlay
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9f537a756e)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-19 01:23:37 +02:00
77541afeab contrib/completion: remove aufs, legacy overlay
The AuFS and (legacy) overlay storage drivers have been deprecated and
removed, so remove them from the completion scripts.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 73fbcdea05)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-19 01:01:55 +02:00
f4b354f688 Merge pull request #4299 from thaJeztah/24.0_backport_drop_the_dot
[24.0 backport] docs/deprecated: remove .patch release from deprecation status
2023-05-18 22:47:36 +01:00
e67a7acd06 docs/deprecated: remove .patch release from deprecation status
commit de8b696ed6 removed the patch
releases from the deprecation doc, but when we switched to the
SemVer(ish) format for v23.0, we accidentally added them back.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 6460eea54d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-18 22:49:56 +02:00
98fdcd769b Merge pull request #4287 from thaJeztah/24.0_update_engine2
Some checks failed
build / prepare (push) Has been cancelled
build / build (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
e2e / e2e (19.03-dind, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, experimental) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, non-experimental) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, connhelper-ssh) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, experimental) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, non-experimental) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-11) (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
[24.0] vendor: github.com/docker/docker v24.0.0-rc.3
2023-05-12 15:10:05 +01:00
fb6ae356c7 vendor: github.com/docker/docker v24.0.0-rc.3
no changes in vendored files

full diff: https://github.com/docker/docker/compare/v24.0.0-rc.2...v24.0.0-rc.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-12 00:03:10 +02:00
1d7dd91593 Merge pull request #4286 from thaJeztah/24.0_backport_vendor_distribution_v2.8.2
Some checks failed
build / prepare (push) Has been cancelled
build / build (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
e2e / e2e (19.03-dind, non-experimental) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, connhelper-ssh) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, experimental) (push) Has been cancelled
e2e / e2e (alpine, stable-dind, non-experimental) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, connhelper-ssh) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, experimental) (push) Has been cancelled
e2e / e2e (bullseye, stable-dind, non-experimental) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-11) (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
[24.0 backport] vendor: github.com/docker/distribution v2.8.2
2023-05-11 19:49:59 +02:00
de93c9b260 vendor: github.com/docker/distribution v2.8.2
CI

- Dockerfile: fix filenames of artifacts

Bugfixes

-  Fix panic in inmemory driver
-  Add code to handle pagination of parts. Fixes max layer size of 10GB bug
-  Parse http forbidden as denied
-  Revert "registry/client: set Accept: identity header when getting layers

Runtime

- Update to go1.19.9
- Dockerfile: update xx to v1.2.1 ([#3907](https://github.com/distribution/distribution/pull/3907))

Security

- Fix [CVE-2022-28391](https://www.cve.org/CVERecord?id=CVE-2022-28391) by bumping alpine from 3.14 to 3.16
- Fix [CVE-2023-2253](https://www.cve.org/CVERecord?id=CVE-2023-2253) runaway allocation on /v2/_catalog [`521ea3d9`](521ea3d973)

full diff: https://github.com/docker/distribution/compare/v2.8.1...v2.8.2-beta.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 353e0a942d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-11 19:16:03 +02:00
75f2669d56 Merge pull request #4277 from thaJeztah/24.0_backport_fix_cli_plugins_metadata_experimental_deprecation
[24.0 backport] cli-plugins/manager: fix deprecation comment of Metadata.Experimental
2023-05-11 16:42:02 +02:00
46615e8724 Merge pull request #4275 from thaJeztah/24.0_backport_update_tag_documentation
[24.0 backport] Update tag docs to clarify name
2023-05-10 21:14:31 +02:00
cafdcf283e cli-plugins/manager: fix deprecation comment of Metadata.Experimental
This field was marked deprecated in 977d3ae046,
which is part of v20.10 and up, but the comment was missing a newline before
the deprecation message, which may be picked up by IDEs, but is not matching
the correct format, so may not be picked up by linters.

This patch fixes the format, to make sure linters pick up that the field is
deprecated.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 72e3813ab9)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-09 22:23:10 +02:00
3768143c2e Update tag docs to clarify name
Signed-off-by: Craig Osterhout <craig.osterhout@docker.com>
(cherry picked from commit 4119d268e7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-09 22:08:47 +02:00
59e9fbd497 Merge pull request #4271 from dvdksn/24.0_backport_docs/host-flag
[24.0 Backport] docs: add description and examples for docker -H
2023-05-08 15:33:22 +02:00
52ac1a974c docs: update description for docker -H flag
Signed-off-by: David Karlsson <david.karlsson@docker.com>
(cherry picked from commit 759fa585cf)
Signed-off-by: David Karlsson <david.karlsson@docker.com>
2023-05-08 15:16:07 +02:00
f25ae85b8e Merge pull request #4264 from thaJeztah/24.0_backport_vendor_docker_24.0.0-rc.2
[24.0 backport] vendor: github.com/docker/docker v24.0.0-rc.2
2023-05-08 08:56:26 +02:00
58f37f630c vendor: github.com/docker/docker v24.0.0-rc.2
no diff, because it's the same as the previous commit, but now tagged;

8d9a40a820...v24.0.0-rc.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 1d8e2b6525)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-06 13:55:17 +02:00
1781 changed files with 30931 additions and 94694 deletions

View File

@ -4,9 +4,6 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
VERSION: ${{ github.ref }}
on:
workflow_dispatch:
push:
@ -25,7 +22,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Create matrix
id: platforms
@ -53,15 +50,15 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
fetch-depth: 0
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
-
name: Build
uses: docker/bake-action@v4
uses: docker/bake-action@v3
with:
targets: ${{ matrix.target }}
set: |
@ -89,50 +86,6 @@ jobs:
path: /tmp/out/*
if-no-files-found: error
bin-image:
runs-on: ubuntu-20.04
if: ${{ github.event_name != 'pull_request' && github.repository == 'docker/cli' }}
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: dockereng/cli-bin
tags: |
type=semver,pattern={{version}}
type=ref,event=branch
type=ref,event=pr
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@v4
with:
files: |
./docker-bake.hcl
${{ steps.meta.outputs.bake-file }}
targets: bin-image-cross
push: ${{ github.event_name != 'pull_request' }}
set: |
*.cache-from=type=gha,scope=bin-image
*.cache-to=type=gha,scope=bin-image,mode=max
prepare-plugins:
runs-on: ubuntu-20.04
outputs:
@ -140,7 +93,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Create matrix
id: platforms
@ -162,13 +115,13 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
-
name: Build
uses: docker/bake-action@v4
uses: docker/bake-action@v3
with:
targets: plugins-cross
set: |

View File

@ -1,15 +1,6 @@
name: codeql
on:
push:
branches:
- 'master'
- '[0-9]+.[0-9]+'
tags:
- 'v*'
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
@ -24,17 +15,11 @@ on:
jobs:
codeql:
runs-on: 'ubuntu-latest'
timeout-minutes: 360
permissions:
actions: read
contents: read
security-events: write
runs-on: ubuntu-20.04
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
fetch-depth: 2
-
@ -42,21 +27,14 @@ jobs:
if: ${{ github.event_name == 'pull_request' }}
run: |
git checkout HEAD^2
-
name: Update Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
-
name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v2
with:
languages: go
-
name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v2
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:go"
uses: github/codeql-action/analyze@v2

View File

@ -26,7 +26,7 @@ jobs:
- connhelper-ssh
base:
- alpine
- debian
- bullseye
engine-version:
# - 20.10-dind # FIXME: Fails on 20.10
- stable-dind # TODO: Use 20.10-dind, stable-dind is deprecated
@ -36,7 +36,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Update daemon.json
run: |
@ -48,7 +48,7 @@ jobs:
docker info
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
-
name: Run ${{ matrix.target }}
run: |

View File

@ -20,13 +20,13 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
-
name: Test
uses: docker/bake-action@v4
uses: docker/bake-action@v3
with:
targets: test-coverage
-
@ -45,7 +45,7 @@ jobs:
fail-fast: false
matrix:
os:
- macos-12
- macos-11
# - 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:
-
@ -56,14 +56,14 @@ jobs:
git config --system core.eol lf
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
path: ${{ env.GOPATH }}/src/github.com/docker/cli
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: 1.21.6
go-version: 1.20.5
-
name: Test
run: |

View File

@ -28,10 +28,10 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Run
uses: docker/bake-action@v4
uses: docker/bake-action@v3
with:
targets: ${{ matrix.target }}
@ -41,7 +41,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Generate
shell: 'script --return --quiet --command "bash {0}"'
@ -67,7 +67,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Run
shell: 'script --return --quiet --command "bash {0}"'

View File

@ -3,41 +3,23 @@ linters:
- bodyclose
- depguard
- dogsled
- dupword # Detects duplicate words.
- durationcheck
- errchkjson
- exportloopref # Detects pointers to enclosing loop variables.
- gocritic # Metalinter; detects bugs, performance, and styling issues.
- gocyclo
- gofumpt # Detects whether code was gofumpt-ed.
- gofumpt
- goimports
- gosec # Detects security problems.
- gosec
- gosimple
- govet
- ineffassign
- lll
- megacheck
- misspell # Detects commonly misspelled English words in comments.
- misspell
- 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.
- revive
- staticcheck
- 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.
- unconvert
- unparam
- unused
- usestdlibvars
- vet
- wastedassign
disable:
- errcheck
@ -50,43 +32,22 @@ run:
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
list-type: blacklist
include-go-root: true
packages:
# The io/ioutil package has been deprecated.
# https://go.dev/doc/go1.16#ioutil
- io/ioutil
gocyclo:
min-complexity: 16
govet:
check-shadowing: true
settings:
shadow:
strict: true
check-shadowing: false
lll:
line-length: 200
nakedret:
command: nakedret
pattern: ^(?P<path>.*?\\.go):(?P<line>\\d+)\\s*(?P<message>.*)$
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
issues:
# The default exclusion rules are a bit too permissive, so copying the relevant ones below
exclude-use-default: false
@ -123,7 +84,7 @@ issues:
- gosec
# EXC0008
# TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close" (gosec)
- text: "G307"
- text: "(G104|G307)"
linters:
- gosec
# EXC0009
@ -137,13 +98,10 @@ issues:
# G113 Potential uncontrolled memory consumption in Rat.SetString (CVE-2022-23772)
# only affects gp < 1.16.14. and go < 1.17.7
- text: "G113"
linters:
- gosec
# TODO: G104: Errors unhandled. (gosec)
- text: "G104"
- text: "(G113)"
linters:
- gosec
# Looks like the match in "EXC0007" above doesn't catch this one
# TODO: consider upstreaming this to golangci-lint's default exclusion rules
- text: "G204: Subprocess launched with a potential tainted input or cmd arguments"
@ -159,24 +117,12 @@ issues:
- 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
# 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
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-issues-per-linter: 0

View File

@ -22,8 +22,6 @@ Akihiro Matsushima <amatsusbit@gmail.com> <amatsus@users.noreply.github.com>
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> <suda.akihiro@lab.ntt.co.jp>
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> <suda.kyoto@gmail.com>
Albin Kerouanton <albinker@gmail.com>
Albin Kerouanton <albinker@gmail.com> <albin@akerouanton.name>
Aleksa Sarai <asarai@suse.de>
Aleksa Sarai <asarai@suse.de> <asarai@suse.com>
Aleksa Sarai <asarai@suse.de> <cyphar@cyphar.com>
@ -31,7 +29,6 @@ Aleksandrs Fadins <aleks@s-ko.net>
Alessandro Boch <aboch@tetrationanalytics.com> <aboch@docker.com>
Alex Chen <alexchenunix@gmail.com> <root@localhost.localdomain>
Alex Ellis <alexellis2@gmail.com>
Alexander Chneerov <achneerov@gmail.com>
Alexander Larsson <alexl@redhat.com> <alexander.larsson@gmail.com>
Alexander Morozov <lk4d4math@gmail.com>
Alexander Morozov <lk4d4math@gmail.com> <lk4d4@docker.com>
@ -75,9 +72,6 @@ Bill Wang <ozbillwang@gmail.com> <SydOps@users.noreply.github.com>
Bin Liu <liubin0329@gmail.com>
Bin Liu <liubin0329@gmail.com> <liubin0329@users.noreply.github.com>
Bingshen Wang <bingshen.wbs@alibaba-inc.com>
Bjorn Neergaard <bjorn.neergaard@docker.com>
Bjorn Neergaard <bjorn.neergaard@docker.com> <bjorn@neersighted.com>
Bjorn Neergaard <bjorn.neergaard@docker.com> <bneergaard@mirantis.com>
Boaz Shuster <ripcurld.github@gmail.com>
Brad Baker <brad@brad.fi>
Brad Baker <brad@brad.fi> <88946291+brdbkr@users.noreply.github.com>
@ -87,7 +81,6 @@ Brent Salisbury <brent.salisbury@docker.com> <brent@docker.com>
Brian Goff <cpuguy83@gmail.com>
Brian Goff <cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.home>
Brian Goff <cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.local>
Brian Tracy <brian.tracy33@gmail.com>
Carlos de Paula <me@carlosedp.com>
Chad Faragher <wyckster@hotmail.com>
Chander Govindarajan <chandergovind@gmail.com>
@ -98,8 +91,6 @@ Chen Chuanliang <chen.chuanliang@zte.com.cn>
Chen Mingjie <chenmingjie0828@163.com>
Chen Qiu <cheney-90@hotmail.com>
Chen Qiu <cheney-90@hotmail.com> <21321229@zju.edu.cn>
Chris Chinchilla <chris@chrischinchilla.com>
Chris Chinchilla <chris@chrischinchilla.com> <chris.ward@docker.com>
Chris Dias <cdias@microsoft.com>
Chris McKinnel <chris.mckinnel@tangentlabs.co.uk>
Christopher Biscardi <biscarch@sketcht.com>
@ -110,7 +101,6 @@ Chun Chen <ramichen@tencent.com> <chenchun.feed@gmail.com>
Comical Derskeal <27731088+derskeal@users.noreply.github.com>
Corbin Coleman <corbin.coleman@docker.com>
Cory Bennet <cbennett@netflix.com>
Craig Osterhout <craig.osterhout@docker.com>
Cristian Staretu <cristian.staretu@gmail.com>
Cristian Staretu <cristian.staretu@gmail.com> <unclejack@users.noreply.github.com>
Cristian Staretu <cristian.staretu@gmail.com> <unclejacksons@gmail.com>
@ -120,7 +110,6 @@ Daehyeok Mun <daehyeok@gmail.com> <daehyeok@daehyeok-ui-MacBook-Air.local>
Daehyeok Mun <daehyeok@gmail.com> <daehyeok@daehyeokui-MacBook-Air.local>
Daisuke Ito <itodaisuke00@gmail.com>
Dan Feldman <danf@jfrog.com>
Danial Gharib <danial.mail.gh@gmail.com>
Daniel Dao <dqminh@cloudflare.com>
Daniel Dao <dqminh@cloudflare.com> <dqminh89@gmail.com>
Daniel Garcia <daniel@danielgarcia.info>
@ -142,8 +131,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 Karlsson <david.karlsson@docker.com>
David Karlsson <david.karlsson@docker.com> <35727626+dvdksn@users.noreply.github.com>
David M. Karr <davidmichaelkarr@gmail.com>
David Sheets <dsheets@docker.com> <sheets@alum.mit.edu>
David Sissitka <me@dsissitka.com>
@ -194,8 +181,6 @@ Gerwim Feiken <g.feiken@tfe.nl> <gerwim@gmail.com>
Giampaolo Mancini <giampaolo@trampolineup.com>
Gopikannan Venugopalsamy <gopikannan.venugopalsamy@gmail.com>
Gou Rao <gou@portworx.com> <gourao@users.noreply.github.com>
Graeme Wiebe <graeme.wiebe@gmail.com>
Graeme Wiebe <graeme.wiebe@gmail.com> <79593869+TheRealGramdalf@users.noreply.github.com>
Greg Stephens <greg@udon.org>
Guillaume J. Charmes <guillaume.charmes@docker.com> <charmes.guillaume@gmail.com>
Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume.charmes@dotcloud.com>
@ -304,8 +289,7 @@ Kelton Bassingthwaite <KeltonBassingthwaite@gmail.com> <github@bassingthwaite.or
Ken Cochrane <kencochrane@gmail.com> <KenCochrane@gmail.com>
Ken Herner <kherner@progress.com> <chosenken@gmail.com>
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
Kevin Alvarez <github@crazymax.dev>
Kevin Alvarez <github@crazymax.dev> <crazy-max@users.noreply.github.com>
Kevin Alvarez <crazy-max@users.noreply.github.com>
Kevin Feyrer <kevin.feyrer@btinternet.com> <kevinfeyrer@users.noreply.github.com>
Kevin Kern <kaiwentan@harmonycloud.cn>
Kevin Meredith <kevin.m.meredith@gmail.com>
@ -322,7 +306,6 @@ Kyle Mitofsky <Kylemit@gmail.com>
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>
Liang Mingqiang <mqliang.zju@gmail.com>
Liang-Chi Hsieh <viirya@gmail.com>
Liao Qingwei <liaoqingwei@huawei.com>
@ -347,7 +330,6 @@ Mansi Nahar <mmn4185@rit.edu> <mansi.nahar@macbookpro-mansinahar.local>
Mansi Nahar <mmn4185@rit.edu> <mansinahar@users.noreply.github.com>
Marc Abramowitz <marc@marc-abramowitz.com> <msabramo@gmail.com>
Marcelo Horacio Fortino <info@fortinux.com> <fortinux@users.noreply.github.com>
Marco Spiess <marco.spiess@hotmail.de>
Marcus Linke <marcus.linke@gmx.de>
Marianna Tessel <mtesselh@gmail.com>
Marius Ileana <marius.ileana@gmail.com>
@ -417,9 +399,6 @@ Paul Liljenberg <liljenberg.paul@gmail.com> <letters@paulnotcom.se>
Pavel Tikhomirov <ptikhomirov@virtuozzo.com> <ptikhomirov@parallels.com>
Pawel Konczalski <mail@konczalski.de>
Paweł Pokrywka <pepawel@users.noreply.github.com>
Per Lundberg <perlun@gmail.com>
Per Lundberg <perlun@gmail.com> <per.lundberg@ecraft.com>
Per Lundberg <perlun@gmail.com> <per.lundberg@hibox.tv>
Peter Choi <phkchoi89@gmail.com> <reikani@Peters-MacBook-Pro.local>
Peter Dave Hello <hsu@peterdavehello.org> <PeterDaveHello@users.noreply.github.com>
Peter Hsu <shhsu@microsoft.com>
@ -435,8 +414,6 @@ Qiang Huang <h.huangqiang@huawei.com>
Qiang Huang <h.huangqiang@huawei.com> <qhuang@10.0.2.15>
Ray Tsang <rayt@google.com> <saturnism@users.noreply.github.com>
Renaud Gaubert <rgaubert@nvidia.com> <renaud.gaubert@gmail.com>
Rob Murray <rob.murray@docker.com>
Rob Murray <rob.murray@docker.com> <148866618+robmry@users.noreply.github.com>
Robert Terhaar <rterhaar@atlanticdynamic.com> <robbyt@users.noreply.github.com>
Roberto G. Hashioka <roberto.hashioka@docker.com> <roberto_hashioka@hotmail.com>
Roberto Muñoz Fernández <robertomf@gmail.com> <roberto.munoz.fernandez.contractor@bbva.com>
@ -452,7 +429,6 @@ Sandeep Bansal <sabansal@microsoft.com>
Sandeep Bansal <sabansal@microsoft.com> <msabansal@microsoft.com>
Sandro Jäckel <sandro.jaeckel@gmail.com>
Sargun Dhillon <sargun@netflix.com> <sargun@sargun.me>
Saurabh Kumar <saurabhkumar0184@gmail.com>
Sean Lee <seanlee@tw.ibm.com> <scaleoutsean@users.noreply.github.com>
Sebastiaan van Stijn <github@gone.nl> <sebastiaan@ws-key-sebas3.dpi1.dpi>
Sebastiaan van Stijn <github@gone.nl> <thaJeztah@users.noreply.github.com>

46
AUTHORS
View File

@ -2,7 +2,6 @@
# This file lists all contributors to the repository.
# See scripts/docs/generate-authors.sh to make modifications.
A. Lester Buck III <github-reg@nbolt.com>
Aanand Prasad <aanand.prasad@gmail.com>
Aaron L. Xu <liker.xu@foxmail.com>
Aaron Lehmann <alehmann@netflix.com>
@ -17,7 +16,6 @@ Adolfo Ochagavía <aochagavia92@gmail.com>
Adrian Plata <adrian.plata@docker.com>
Adrien Duermael <adrien@duermael.com>
Adrien Folie <folie.adrien@gmail.com>
Adyanth Hosavalike <ahosavalike@ucsd.edu>
Ahmet Alp Balkan <ahmetb@microsoft.com>
Aidan Feldman <aidan.feldman@gmail.com>
Aidan Hobson Sayers <aidanhs@cantab.net>
@ -28,7 +26,7 @@ Akim Demaille <akim.demaille@docker.com>
Alan Thompson <cloojure@gmail.com>
Albert Callarisa <shark234@gmail.com>
Alberto Roura <mail@albertoroura.com>
Albin Kerouanton <albinker@gmail.com>
Albin Kerouanton <albin@akerouanton.name>
Aleksa Sarai <asarai@suse.de>
Aleksander Piotrowski <apiotrowski312@gmail.com>
Alessandro Boch <aboch@tetrationanalytics.com>
@ -36,7 +34,6 @@ Alex Couture-Beil <alex@earthly.dev>
Alex Mavrogiannis <alex.mavrogiannis@docker.com>
Alex Mayer <amayer5125@gmail.com>
Alexander Boyd <alex@opengroove.org>
Alexander Chneerov <achneerov@gmail.com>
Alexander Larsson <alexl@redhat.com>
Alexander Morozov <lk4d4math@gmail.com>
Alexander Ryabov <i@sepa.spb.ru>
@ -44,7 +41,6 @@ Alexandre González <agonzalezro@gmail.com>
Alexey Igrychev <alexey.igrychev@flant.com>
Alexis Couvreur <alexiscouvreur.pro@gmail.com>
Alfred Landrum <alfred.landrum@docker.com>
Ali Rostami <rostami.ali@gmail.com>
Alicia Lauerman <alicia@eta.im>
Allen Sun <allensun.shl@alibaba-inc.com>
Alvin Deng <alvin.q.deng@utexas.edu>
@ -83,9 +79,7 @@ Arko Dasgupta <arko@tetrate.io>
Arnaud Porterie <icecrime@gmail.com>
Arnaud Rebillout <elboulangero@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>
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
Bardia Keyoumarsi <bkeyouma@ucsc.edu>
Barnaby Gray <barnaby@pickle.me.uk>
@ -104,9 +98,7 @@ Bill Wang <ozbillwang@gmail.com>
Bin Liu <liubin0329@gmail.com>
Bingshen Wang <bingshen.wbs@alibaba-inc.com>
Bishal Das <bishalhnj127@gmail.com>
Bjorn Neergaard <bjorn.neergaard@docker.com>
Boaz Shuster <ripcurld.github@gmail.com>
Boban Acimovic <boban.acimovic@gmail.com>
Bogdan Anton <contact@bogdananton.ro>
Boris Pruessmann <boris@pruessmann.org>
Brad Baker <brad@brad.fi>
@ -117,7 +109,6 @@ Brent Salisbury <brent.salisbury@docker.com>
Bret Fisher <bret@bretfisher.com>
Brian (bex) Exelbierd <bexelbie@redhat.com>
Brian Goff <cpuguy83@gmail.com>
Brian Tracy <brian.tracy33@gmail.com>
Brian Wieder <brian@4wieders.com>
Bruno Sousa <bruno.sousa@docker.com>
Bryan Bess <squarejaw@bsbess.com>
@ -145,7 +136,6 @@ Chen Chuanliang <chen.chuanliang@zte.com.cn>
Chen Hanxiao <chenhanxiao@cn.fujitsu.com>
Chen Mingjie <chenmingjie0828@163.com>
Chen Qiu <cheney-90@hotmail.com>
Chris Chinchilla <chris@chrischinchilla.com>
Chris Couzens <ccouzens@gmail.com>
Chris Gavin <chris@chrisgavin.me>
Chris Gibson <chris@chrisg.io>
@ -173,8 +163,6 @@ Conner Crosby <conner@cavcrosby.tech>
Corey Farrell <git@cfware.com>
Corey Quon <corey.quon@docker.com>
Cory Bennet <cbennett@netflix.com>
Cory Snider <csnider@mirantis.com>
Craig Osterhout <craig.osterhout@docker.com>
Craig Wilhite <crwilhit@microsoft.com>
Cristian Staretu <cristian.staretu@gmail.com>
Daehyeok Mun <daehyeok@gmail.com>
@ -183,7 +171,6 @@ Daisuke Ito <itodaisuke00@gmail.com>
dalanlan <dalanlan925@gmail.com>
Damien Nadé <github@livna.org>
Dan Cotora <dan@bluevision.ro>
Danial Gharib <danial.mail.gh@gmail.com>
Daniel Artine <daniel.artine@ufrj.br>
Daniel Cassidy <mail@danielcassidy.me.uk>
Daniel Dao <dqminh@cloudflare.com>
@ -223,7 +210,6 @@ Denis Defreyne <denis@soundcloud.com>
Denis Gladkikh <denis@gladkikh.email>
Denis Ollier <larchunix@users.noreply.github.com>
Dennis Docter <dennis@d23.nl>
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Derek McGowan <derek@mcg.dev>
Des Preston <despreston@gmail.com>
Deshi Xiao <dxiao@redhat.com>
@ -246,13 +232,11 @@ DongGeon Lee <secmatth1996@gmail.com>
Doug Davis <dug@us.ibm.com>
Drew Erny <derny@mirantis.com>
Ed Costello <epc@epcostello.com>
Ed Morley <501702+edmorley@users.noreply.github.com>
Elango Sivanandam <elango.siva@docker.com>
Eli Uriegas <eli.uriegas@docker.com>
Eli Uriegas <seemethere101@gmail.com>
Elias Faxö <elias.faxo@tre.se>
Elliot Luo <956941328@qq.com>
Eric Bode <eric.bode@foundries.io>
Eric Curtin <ericcurtin17@gmail.com>
Eric Engestrom <eric@engestrom.ch>
Eric G. Noriega <enoriega@vizuri.com>
@ -270,7 +254,6 @@ Eugene Yakubovich <eugene.yakubovich@coreos.com>
Evan Allrich <evan@unguku.com>
Evan Hazlett <ejhazlett@gmail.com>
Evan Krall <krall@yelp.com>
Evan Lezar <elezar@nvidia.com>
Evelyn Xu <evelynhsu21@gmail.com>
Everett Toews <everett.toews@rackspace.com>
Fabio Falci <fabiofalci@gmail.com>
@ -292,7 +275,6 @@ Frederik Nordahl Jul Sabroe <frederikns@gmail.com>
Frieder Bluemle <frieder.bluemle@gmail.com>
Gabriel Gore <gabgore@cisco.com>
Gabriel Nicolas Avellaneda <avellaneda.gabriel@gmail.com>
Gabriela Georgieva <gabriela.georgieva@docker.com>
Gaetan de Villele <gdevillele@gmail.com>
Gang Qiao <qiaohai8866@gmail.com>
Gary Schaetz <gary@schaetzkc.com>
@ -306,7 +288,6 @@ Gleb Stsenov <gleb.stsenov@gmail.com>
Goksu Toprak <goksu.toprak@docker.com>
Gou Rao <gou@portworx.com>
Govind Rai <raigovind93@gmail.com>
Graeme Wiebe <graeme.wiebe@gmail.com>
Grant Reaber <grant.reaber@gmail.com>
Greg Pflaum <gpflaum@users.noreply.github.com>
Gsealy <jiaojingwei1001@hotmail.com>
@ -330,7 +311,6 @@ Hernan Garcia <hernandanielg@gmail.com>
Hongbin Lu <hongbin034@gmail.com>
Hu Keping <hukeping@huawei.com>
Huayi Zhang <irachex@gmail.com>
Hugo Chastel <Hugo-C@users.noreply.github.com>
Hugo Gabriel Eyherabide <hugogabriel.eyherabide@gmail.com>
huqun <huqun@zju.edu.cn>
Huu Nguyen <huu@prismskylabs.com>
@ -349,12 +329,9 @@ Ivan Grund <ivan.grund@gmail.com>
Ivan Markin <sw@nogoegst.net>
Jacob Atzen <jacob@jacobatzen.dk>
Jacob Tomlinson <jacob@tom.linson.uk>
Jacopo Rigoli <rigoli.jacopo@gmail.com>
Jaivish Kothari <janonymous.codevulture@gmail.com>
Jake Lambert <jake.lambert@volusion.com>
Jake Sanders <jsand@google.com>
Jake Stokes <contactjake@developerjake.com>
Jakub Panek <me@panekj.dev>
James Nesbitt <james.nesbitt@wunderkraut.com>
James Turnbull <james@lovedthanlost.net>
Jamie Hannaford <jamie@limetree.org>
@ -431,12 +408,10 @@ Josh Chorlton <jchorlton@gmail.com>
Josh Hawn <josh.hawn@docker.com>
Josh Horwitz <horwitz@addthis.com>
Josh Soref <jsoref@gmail.com>
Julian <gitea+julian@ic.thejulian.uk>
Julien Barbier <write0@gmail.com>
Julien Kassar <github@kassisol.com>
Julien Maitrehenry <julien.maitrehenry@me.com>
Justas Brazauskas <brazauskasjustas@gmail.com>
Justin Chadwell <me@jedevc.com>
Justin Cormack <justin.cormack@docker.com>
Justin Simonelis <justin.p.simonelis@gmail.com>
Justyn Temme <justyntemme@gmail.com>
@ -459,7 +434,7 @@ Kelton Bassingthwaite <KeltonBassingthwaite@gmail.com>
Ken Cochrane <kencochrane@gmail.com>
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
Kevin Alvarez <github@crazymax.dev>
Kevin Alvarez <crazy-max@users.noreply.github.com>
Kevin Burke <kev@inburke.com>
Kevin Feyrer <kevin.feyrer@btinternet.com>
Kevin Kern <kaiwentan@harmonycloud.cn>
@ -479,7 +454,6 @@ Kyle Mitofsky <Kylemit@gmail.com>
Lachlan Cooper <lachlancooper@gmail.com>
Lai Jiangshan <jiangshanlai@gmail.com>
Lars Kellogg-Stedman <lars@redhat.com>
Laura Brehm <laurabrehm@hey.com>
Laura Frank <ljfrank@gmail.com>
Laurent Erignoux <lerignoux@gmail.com>
Lee Gaines <eightlimbed@gmail.com>
@ -488,10 +462,10 @@ Lennie <github@consolejunkie.net>
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 Yi <weiyuan.yl@alibaba-inc.com>
Liang-Chi Hsieh <viirya@gmail.com>
Lifubang <lifubang@acmcoder.com>
Lihua Tang <lhtang@alauda.io>
Lily Guo <lily.guo@docker.com>
Lin Lu <doraalin@163.com>
@ -506,7 +480,6 @@ Louis Opter <kalessin@kalessin.fr>
Luca Favatella <luca.favatella@erlang-solutions.com>
Luca Marturana <lucamarturana@gmail.com>
Lucas Chan <lucas-github@lucaschan.com>
Luis Henrique Mulinari <luis.mulinari@gmail.com>
Luka Hartwig <mail@lukahartwig.de>
Lukas Heeren <lukas-heeren@hotmail.com>
Lukasz Zajaczkowski <Lukasz.Zajaczkowski@ts.fujitsu.com>
@ -525,7 +498,6 @@ mapk0y <mapk0y@gmail.com>
Marc Bihlmaier <marc.bihlmaier@reddoxx.com>
Marc Cornellà <hello@mcornella.com>
Marco Mariani <marco.mariani@alterway.fr>
Marco Spiess <marco.spiess@hotmail.de>
Marco Vedovati <mvedovati@suse.com>
Marcus Martins <marcus@docker.com>
Marianna Tessel <mtesselh@gmail.com>
@ -550,7 +522,6 @@ Max Shytikov <mshytikov@gmail.com>
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>
Metal <2466052+tedhexaflow@users.noreply.github.com>
Micah Zoltu <micah@newrelic.com>
Michael A. Smith <michael@smith-li.com>
@ -622,7 +593,6 @@ Nishant Totla <nishanttotla@gmail.com>
NIWA Hideyuki <niwa.niwa@nifty.ne.jp>
Noah Treuhaft <noah.treuhaft@docker.com>
O.S. Tezer <ostezer@gmail.com>
Oded Arbel <oded@geek.co.il>
Odin Ugedal <odin@ugedal.com>
ohmystack <jun.jiang02@ele.me>
OKA Naoya <git@okanaoya.com>
@ -634,21 +604,19 @@ Otto Kekäläinen <otto@seravo.fi>
Ovidio Mallo <ovidio.mallo@gmail.com>
Pascal Borreli <pascal@borreli.com>
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>
Paul <paul9869@gmail.com>
Paul Kehrer <paul.l.kehrer@gmail.com>
Paul Lietar <paul@lietar.net>
Paul Mulders <justinkb@gmail.com>
Paul Seyfert <pseyfert.mathphys@gmail.com>
Paul Weaver <pauweave@cisco.com>
Pavel Pospisil <pospispa@gmail.com>
Paweł Gronowski <pawel.gronowski@docker.com>
Paweł Pokrywka <pepawel@users.noreply.github.com>
Paweł Szczekutowicz <pszczekutowicz@gmail.com>
Peeyush Gupta <gpeeyush@linux.vnet.ibm.com>
Per Lundberg <perlun@gmail.com>
Per Lundberg <per.lundberg@ecraft.com>
Peter Dave Hello <hsu@peterdavehello.org>
Peter Edge <peter.edge@gmail.com>
Peter Hsu <shhsu@microsoft.com>
@ -671,7 +639,6 @@ Preston Cowley <preston.cowley@sony.com>
Pure White <daniel48@126.com>
Qiang Huang <h.huangqiang@huawei.com>
Qinglan Peng <qinglanpeng@zju.edu.cn>
QQ喵 <gqqnb2005@gmail.com>
qudongfang <qudongfang@gmail.com>
Raghavendra K T <raghavendra.kt@linux.vnet.ibm.com>
Rahul Kadyan <hi@znck.me>
@ -690,7 +657,6 @@ Rick Wieman <git@rickw.nl>
Ritesh H Shukla <sritesh@vmware.com>
Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Rob Gulewich <rgulewich@netflix.com>
Rob Murray <rob.murray@docker.com>
Robert Wallis <smilingrob@gmail.com>
Robin Naundorf <r.naundorf@fh-muenster.de>
Robin Speekenbrink <robin@kingsquare.nl>
@ -723,7 +689,6 @@ Sandro Jäckel <sandro.jaeckel@gmail.com>
Santhosh Manohar <santhosh@docker.com>
Sargun Dhillon <sargun@netflix.com>
Saswat Bhattacharya <sas.saswat@gmail.com>
Saurabh Kumar <saurabhkumar0184@gmail.com>
Scott Brenner <scott@scottbrenner.me>
Scott Collier <emailscottcollier@gmail.com>
Sean Christopherson <sean.j.christopherson@intel.com>
@ -823,7 +788,6 @@ uhayate <uhayate.gong@daocloud.io>
Ulrich Bareth <ulrich.bareth@gmail.com>
Ulysses Souza <ulysses.souza@docker.com>
Umesh Yadav <umesh4257@gmail.com>
Vaclav Struhar <struharv@gmail.com>
Valentin Lorentz <progval+git@progval.net>
Vardan Pogosian <vardan.pogosyan@gmail.com>
Venkateswara Reddy Bukkasamudram <bukkasamudram@outlook.com>
@ -831,7 +795,6 @@ Veres Lajos <vlajos@gmail.com>
Victor Vieux <victor.vieux@docker.com>
Victoria Bialas <victoria.bialas@docker.com>
Viktor Stanchev <me@viktorstanchev.com>
Ville Skyttä <ville.skytta@iki.fi>
Vimal Raghubir <vraghubir0418@gmail.com>
Vincent Batts <vbatts@redhat.com>
Vincent Bernat <Vincent.Bernat@exoscale.ch>
@ -868,7 +831,6 @@ Yong Tang <yong.tang.github@outlook.com>
Yosef Fertel <yfertel@gmail.com>
Yu Peng <yu.peng36@zte.com.cn>
Yuan Sun <sunyuan3@huawei.com>
Yucheng Wu <wyc123wyc@gmail.com>
Yue Zhang <zy675793960@yeah.net>
Yunxiang Huang <hyxqshk@vip.qq.com>
Zachary Romero <zacromero3@gmail.com>

View File

@ -1,5 +1,9 @@
# Contributing to Docker
Want to hack on Docker? Awesome! We have a contributor's guide that explains
[setting up a Docker development environment and the contribution
process](https://docs.docker.com/opensource/project/who-written-for/).
This page contains information about reporting issues as well as some tips and
guidelines useful to experienced open source contributors. Finally, make sure
you read our [community guidelines](#docker-community-guidelines) before you
@ -188,7 +192,7 @@ For more details, see the [MAINTAINERS](MAINTAINERS) page.
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](https://developercertificate.org):
the below (from [developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
@ -332,8 +336,9 @@ The rules:
1. All code should be formatted with `gofumpt` (preferred) or `gofmt -s`.
2. All code should pass the default levels of
[`golint`](https://github.com/golang/lint).
3. All code should follow the guidelines covered in [Effective Go](https://go.dev/doc/effective_go)
and [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments).
3. All code should follow the guidelines covered in [Effective
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
4. Comment the code. Tell us the why, the history and the context.
5. Document _all_ declarations and methods, even private ones. Declare
expectations, caveats and anything else that may be important. If a type
@ -355,6 +360,6 @@ The rules:
guidelines. Since you've read all the rules, you now know that.
If you are having trouble getting into the mood of idiomatic Go, we recommend
reading through [Effective Go](https://go.dev/doc/effective_go). The
[Go Blog](https://go.dev/blog/) is also a great resource. Drinking the
reading through [Effective Go](https://golang.org/doc/effective_go.html). The
[Go Blog](https://blog.golang.org) is also a great resource. Drinking the
kool-aid is a lot easier than going thirsty.

View File

@ -1,21 +1,17 @@
# syntax=docker/dockerfile:1
ARG BASE_VARIANT=alpine
ARG ALPINE_VERSION=3.18
ARG BASE_DEBIAN_DISTRO=bookworm
ARG GO_VERSION=1.21.6
ARG XX_VERSION=1.2.1
ARG GO_VERSION=1.20.5
ARG ALPINE_VERSION=3.17
ARG XX_VERSION=1.1.1
ARG GOVERSIONINFO_VERSION=v1.3.0
ARG GOTESTSUM_VERSION=v1.10.0
ARG BUILDX_VERSION=0.12.1
ARG COMPOSE_VERSION=v2.24.0
ARG BUILDX_VERSION=0.11.0
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build-base-alpine
ENV GOTOOLCHAIN=local
COPY --link --from=xx / /
COPY --from=xx / /
RUN apk add --no-cache bash clang lld llvm file git
WORKDIR /go/src/github.com/docker/cli
@ -24,27 +20,33 @@ ARG TARGETPLATFORM
# gcc is installed for libgcc only
RUN xx-apk add --no-cache musl-dev gcc
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO} AS build-base-debian
ENV GOTOOLCHAIN=local
COPY --link --from=xx / /
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bullseye AS build-base-bullseye
COPY --from=xx / /
RUN apt-get update && apt-get install --no-install-recommends -y bash clang lld llvm file
WORKDIR /go/src/github.com/docker/cli
FROM build-base-debian AS build-debian
FROM build-base-bullseye AS build-bullseye
ARG TARGETPLATFORM
RUN xx-apt-get install --no-install-recommends -y libc6-dev libgcc-12-dev pkgconf
RUN xx-apt-get install --no-install-recommends -y libc6-dev libgcc-10-dev
# workaround for issue with llvm 11 for darwin/amd64 platform:
# # github.com/docker/cli/cmd/docker
# /usr/local/go/pkg/tool/linux_amd64/link: /usr/local/go/pkg/tool/linux_amd64/link: running strip failed: exit status 1
# llvm-strip: error: unsupported load command (cmd=0x5)
# more info: https://github.com/docker/cli/pull/3717
# FIXME: remove once llvm 12 available on debian
RUN [ "$TARGETPLATFORM" != "darwin/amd64" ] || ln -sfnT /bin/true /usr/bin/llvm-strip
FROM build-base-${BASE_VARIANT} AS goversioninfo
ARG GOVERSIONINFO_VERSION
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
GOBIN=/out GO111MODULE=on CGO_ENABLED=0 go install "github.com/josephspurrier/goversioninfo/cmd/goversioninfo@${GOVERSIONINFO_VERSION}"
GOBIN=/out GO111MODULE=on go install "github.com/josephspurrier/goversioninfo/cmd/goversioninfo@${GOVERSIONINFO_VERSION}"
FROM build-base-${BASE_VARIANT} AS gotestsum
ARG GOTESTSUM_VERSION
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
GOBIN=/out GO111MODULE=on CGO_ENABLED=0 go install "gotest.tools/gotestsum@${GOTESTSUM_VERSION}" \
GOBIN=/out GO111MODULE=on go install "gotest.tools/gotestsum@${GOTESTSUM_VERSION}" \
&& /out/gotestsum --version
FROM build-${BASE_VARIANT} AS build
@ -60,7 +62,9 @@ ARG CGO_ENABLED
ARG VERSION
# PACKAGER_NAME sets the company that produced the windows binary
ARG PACKAGER_NAME
COPY --link --from=goversioninfo /out/goversioninfo /usr/bin/goversioninfo
COPY --from=goversioninfo /out/goversioninfo /usr/bin/goversioninfo
# in bullseye arm64 target does not link with lld so configure it to use ld instead
RUN [ ! -f /etc/alpine-release ] && xx-info is-cross && [ "$(xx-info arch)" = "arm64" ] && XX_CC_PREFER_LINKER=ld xx-clang --setup-target-triple || true
RUN --mount=type=bind,target=.,ro \
--mount=type=cache,target=/root/.cache \
--mount=from=dockercore/golang-cross:xx-sdk-extras,target=/xx-sdk,src=/xx-sdk \
@ -72,7 +76,7 @@ RUN --mount=type=bind,target=.,ro \
xx-verify $([ "$GO_LINKMODE" = "static" ] && echo "--static") /out/docker
FROM build-${BASE_VARIANT} AS test
COPY --link --from=gotestsum /out/gotestsum /usr/bin/gotestsum
COPY --from=gotestsum /out/gotestsum /usr/bin/gotestsum
ENV GO111MODULE=auto
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/root/.cache \
@ -94,37 +98,35 @@ RUN --mount=ro --mount=type=cache,target=/root/.cache \
TARGET=/out ./scripts/build/plugins e2e/cli-plugins/plugins/*
FROM build-base-alpine AS e2e-base-alpine
RUN apk add --no-cache build-base curl openssl openssh-client
RUN apk add --no-cache build-base curl docker-compose openssl openssh-client
FROM build-base-debian AS e2e-base-debian
FROM build-base-bullseye AS e2e-base-bullseye
RUN apt-get update && apt-get install -y build-essential curl openssl openssh-client
ARG COMPOSE_VERSION=1.29.2
RUN curl -fsSL https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose && \
chmod +x /usr/local/bin/docker-compose
FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx
FROM docker/compose-bin:${COMPOSE_VERSION} AS compose
FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx
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
COPY 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/
COPY --link --from=buildx /buildx /usr/libexec/docker/cli-plugins/docker-buildx
COPY --link --from=compose /docker-compose /usr/libexec/docker/cli-plugins/docker-compose
COPY --link . .
COPY --from=gotestsum /out/gotestsum /usr/bin/gotestsum
COPY --from=build /out ./build/
COPY --from=build-plugins /out ./build/
COPY --from=buildx /buildx /usr/libexec/docker/cli-plugins/docker-buildx
COPY . .
ENV DOCKER_BUILDKIT=1
ENV PATH=/go/src/github.com/docker/cli/build:$PATH
CMD ./scripts/test/e2e/entry
FROM build-base-${BASE_VARIANT} AS dev
COPY --link . .
COPY . .
FROM scratch AS plugins
COPY --from=build-plugins /out .
FROM scratch AS bin-image
COPY --from=build /out/docker /docker
FROM scratch AS binary
COPY --from=build /out .

View File

@ -24,6 +24,7 @@
people = [
"albers",
"cpuguy83",
"ndeloof",
"rumpl",
"silvin-lubecki",
"stevvooe",
@ -97,6 +98,11 @@
Email = "dnephin@gmail.com"
GitHub = "dnephin"
[people.ndeloof]
Name = "Nicolas De Loof"
Email = "nicolas.deloof@gmail.com"
GitHub = "ndeloof"
[people.neersighted]
Name = "Bjorn Neergaard"
Email = "bneergaard@mirantis.com"

View File

@ -8,7 +8,8 @@
## About
This repository is the home of the Docker CLI.
This repository is the home of the cli used in the Docker CE and
Docker EE products.
## Development

View File

@ -1 +1 @@
25.0.0-dev
24.0.0-dev

View File

@ -45,8 +45,8 @@ func main() {
}
var (
who, optContext string
preRun, debug bool
who, context string
preRun, debug bool
)
cmd := &cobra.Command{
Use: "helloworld",
@ -65,7 +65,7 @@ func main() {
fmt.Fprintf(dockerCli.Err(), "Plugin debug mode enabled")
}
switch optContext {
switch context {
case "Christmas":
fmt.Fprintf(dockerCli.Out(), "Merry Christmas!\n")
return nil
@ -92,7 +92,7 @@ func main() {
// These are intended to deliberately clash with the CLIs own top
// level arguments.
flags.BoolVarP(&debug, "debug", "D", false, "Enable debug")
flags.StringVarP(&optContext, "context", "c", "", "Is it Christmas?")
flags.StringVarP(&context, "context", "c", "", "Is it Christmas?")
cmd.AddCommand(goodbye, apiversion, exitStatus2)
return cmd

View File

@ -1,6 +1,8 @@
package manager
import "os/exec"
import (
exec "golang.org/x/sys/execabs"
)
// Candidate represents a possible plugin candidate, for mocking purposes
type Candidate interface {

View File

@ -75,14 +75,13 @@ func TestValidateCandidate(t *testing.T) {
} {
t.Run(tc.name, func(t *testing.T) {
p, err := newPlugin(tc.c, fakeroot.Commands())
switch {
case tc.err != "":
if tc.err != "" {
assert.ErrorContains(t, err, tc.err)
case tc.invalid != "":
} else if tc.invalid != "" {
assert.NilError(t, err)
assert.Assert(t, cmp.ErrorType(p.Err, reflect.TypeOf(&pluginError{})))
assert.ErrorContains(t, p.Err, tc.invalid)
default:
} else {
assert.NilError(t, err)
assert.Equal(t, NamePrefix+p.Name, goodPluginName)
assert.Equal(t, p.SchemaVersion, "0.1.0")

View File

@ -1,6 +1,3 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.19
package manager
import (
@ -46,6 +43,6 @@ func wrapAsPluginError(err error, msg string) error {
// NewPluginError creates a new pluginError, analogous to
// errors.Errorf.
func NewPluginError(msg string, args ...any) error {
func NewPluginError(msg string, args ...interface{}) error {
return &pluginError{cause: errors.Errorf(msg, args...)}
}

View File

@ -3,7 +3,6 @@ package manager
import (
"context"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
@ -11,10 +10,10 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/fvbommel/sortorder"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
exec "golang.org/x/sys/execabs"
)
// ReexecEnvvar is the name of an ennvar which is set to the command
@ -43,10 +42,10 @@ func IsNotFound(err error) bool {
return ok
}
func getPluginDirs(cfg *configfile.ConfigFile) ([]string, error) {
func getPluginDirs(dockerCli command.Cli) ([]string, error) {
var pluginDirs []string
if cfg != nil {
if cfg := dockerCli.ConfigFile(); cfg != nil {
pluginDirs = append(pluginDirs, cfg.CLIPluginsExtraDirs...)
}
pluginDir, err := config.Path("cli-plugins")
@ -109,7 +108,7 @@ func listPluginCandidates(dirs []string) (map[string][]string, error) {
// GetPlugin returns a plugin on the system by its name
func GetPlugin(name string, dockerCli command.Cli, rootcmd *cobra.Command) (*Plugin, error) {
pluginDirs, err := getPluginDirs(dockerCli.ConfigFile())
pluginDirs, err := getPluginDirs(dockerCli)
if err != nil {
return nil, err
}
@ -139,7 +138,7 @@ func GetPlugin(name string, dockerCli command.Cli, rootcmd *cobra.Command) (*Plu
// ListPlugins produces a list of the plugins available on the system
func ListPlugins(dockerCli command.Cli, rootcmd *cobra.Command) ([]Plugin, error) {
pluginDirs, err := getPluginDirs(dockerCli.ConfigFile())
pluginDirs, err := getPluginDirs(dockerCli)
if err != nil {
return nil, err
}
@ -199,7 +198,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command
return nil, errPluginNotFound(name)
}
exename := addExeSuffix(NamePrefix + name)
pluginDirs, err := getPluginDirs(dockerCli.ConfigFile())
pluginDirs, err := getPluginDirs(dockerCli)
if err != nil {
return nil, err
}

View File

@ -46,7 +46,7 @@ func TestListPluginCandidates(t *testing.T) {
)
defer dir.Remove()
dirs := make([]string, 0, 6)
var dirs []string
for _, d := range []string{"plugins1", "nonexistent", "plugins2", "plugins3", "plugins4", "plugins5"} {
dirs = append(dirs, dir.Join(d))
}
@ -149,7 +149,7 @@ func TestGetPluginDirs(t *testing.T) {
expected := append([]string{pluginDir}, defaultSystemPluginDirs...)
var pluginDirs []string
pluginDirs, err = getPluginDirs(cli.ConfigFile())
pluginDirs, err = getPluginDirs(cli)
assert.Equal(t, strings.Join(expected, ":"), strings.Join(pluginDirs, ":"))
assert.NilError(t, err)
@ -160,7 +160,7 @@ func TestGetPluginDirs(t *testing.T) {
cli.SetConfigFile(&configfile.ConfigFile{
CLIPluginsExtraDirs: extras,
})
pluginDirs, err = getPluginDirs(cli.ConfigFile())
pluginDirs, err = getPluginDirs(cli)
assert.DeepEqual(t, expected, pluginDirs)
assert.NilError(t, err)
}

View File

@ -1,4 +1,5 @@
//go:build !windows
// +build !windows
package manager

View File

@ -22,4 +22,8 @@ type Metadata struct {
ShortDescription string `json:",omitempty"`
// URL is a pointer to the plugin's homepage.
URL string `json:",omitempty"`
// Experimental specifies whether the plugin is experimental.
//
// Deprecated: experimental features are now always enabled in the CLI
Experimental bool `json:",omitempty"`
}

View File

@ -1,4 +1,5 @@
//go:build !windows
// +build !windows
package manager

View File

@ -1,7 +1,6 @@
package plugin
import (
"context"
"encoding/json"
"fmt"
"os"
@ -9,7 +8,6 @@ import (
"github.com/docker/cli/cli"
"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/docker/client"
@ -31,21 +29,10 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager
tcmd := newPluginCommand(dockerCli, plugin, meta)
var persistentPreRunOnce sync.Once
PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
PersistentPreRunE = func(_ *cobra.Command, _ []string) error {
var err error
persistentPreRunOnce.Do(func() {
cmdContext := cmd.Context()
// TODO: revisit and make sure this check makes sense
// see: https://github.com/docker/cli/pull/4599#discussion_r1422487271
if cmdContext == nil {
cmdContext = context.TODO()
}
ctx, cancel := context.WithCancel(cmdContext)
cmd.SetContext(ctx)
// Set up the context to cancel based on signalling via CLI socket.
socket.ConnectAndWait(cancel)
var opts []command.CLIOption
var opts []command.InitializeOpt
if os.Getenv("DOCKER_CLI_PLUGIN_USE_DIAL_STDIO") != "" {
opts = append(opts, withPluginClientConn(plugin.Name()))
}
@ -91,7 +78,7 @@ func Run(makeCmd func(command.Cli) *cobra.Command, meta manager.Metadata) {
}
}
func withPluginClientConn(name string) command.CLIOption {
func withPluginClientConn(name string) command.InitializeOpt {
return command.WithInitializeClient(func(dockerCli *command.DockerCli) (client.APIClient, error) {
cmd := "docker"
if x := os.Getenv(manager.ReexecEnvvar); x != "" {
@ -144,7 +131,7 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta
DisableDescriptions: true,
},
}
opts, _ := cli.SetupPluginRootCommand(cmd)
opts, flags := cli.SetupPluginRootCommand(cmd)
cmd.SetIn(dockerCli.In())
cmd.SetOut(dockerCli.Out())
@ -157,7 +144,7 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta
cli.DisableFlagsInUseLine(cmd)
return cli.NewTopLevelCommand(cmd, dockerCli, opts, cmd.Flags())
return cli.NewTopLevelCommand(cmd, dockerCli, opts, flags)
}
func newMetadataSubcommand(plugin *cobra.Command, meta manager.Metadata) *cobra.Command {

View File

@ -1,71 +0,0 @@
package socket
import (
"errors"
"io"
"net"
"os"
"github.com/docker/distribution/uuid"
)
// EnvKey represents the well-known environment variable used to pass the plugin being
// executed the socket name it should listen on to coordinate with the host CLI.
const EnvKey = "DOCKER_CLI_PLUGIN_SOCKET"
// SetupConn sets up a Unix socket listener, establishes a goroutine to handle connections
// and update the conn pointer, and returns the listener for the socket (which the caller
// is responsible for closing when it's no longer needed).
func SetupConn(conn **net.UnixConn) (*net.UnixListener, error) {
listener, err := listen("docker_cli_" + uuid.Generate().String())
if err != nil {
return nil, err
}
accept(listener, conn)
return listener, nil
}
func accept(listener *net.UnixListener, conn **net.UnixConn) {
go func() {
for {
// ignore error here, if we failed to accept a connection,
// conn is nil and we fallback to previous behavior
*conn, _ = listener.AcceptUnix()
// perform any platform-specific actions on accept (e.g. unlink non-abstract sockets)
onAccept(*conn, listener)
}
}()
}
// ConnectAndWait connects to the socket passed via well-known env var,
// if present, and attempts to read from it until it receives an EOF, at which
// point cb is called.
func ConnectAndWait(cb func()) {
socketAddr, ok := os.LookupEnv(EnvKey)
if !ok {
// if a plugin compiled against a more recent version of docker/cli
// is executed by an older CLI binary, ignore missing environment
// variable and behave as usual
return
}
addr, err := net.ResolveUnixAddr("unix", socketAddr)
if err != nil {
return
}
conn, err := net.DialUnix("unix", nil, addr)
if err != nil {
return
}
go func() {
b := make([]byte, 1)
for {
_, err := conn.Read(b)
if errors.Is(err, io.EOF) {
cb()
}
}
}()
}

View File

@ -1,19 +0,0 @@
package socket
import (
"net"
"os"
"path/filepath"
"syscall"
)
func listen(socketname string) (*net.UnixListener, error) {
return net.ListenUnix("unix", &net.UnixAddr{
Name: filepath.Join(os.TempDir(), socketname),
Net: "unix",
})
}
func onAccept(conn *net.UnixConn, listener *net.UnixListener) {
syscall.Unlink(listener.Addr().String())
}

View File

@ -1,19 +0,0 @@
//go:build !darwin
package socket
import (
"net"
)
func listen(socketname string) (*net.UnixListener, error) {
return net.ListenUnix("unix", &net.UnixAddr{
Name: "@" + socketname,
Net: "unix",
})
}
func onAccept(conn *net.UnixConn, listener *net.UnixListener) {
// do nothing
// while on darwin we would unlink here; on non-darwin the socket is abstract and not present on the filesystem
}

View File

@ -9,6 +9,7 @@ import (
pluginmanager "github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config"
cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/docker/pkg/homedir"
"github.com/docker/docker/registry"
@ -22,9 +23,12 @@ import (
// setupCommonRootCommand contains the setup common to
// SetupRootCommand and SetupPluginRootCommand.
func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *cobra.Command) {
func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *pflag.FlagSet, *cobra.Command) {
opts := cliflags.NewClientOptions()
opts.InstallFlags(rootCmd.Flags())
flags := rootCmd.Flags()
flags.StringVar(&opts.ConfigDir, "config", config.Dir(), "Location of client config files")
opts.InstallFlags(flags)
cobra.AddTemplateFunc("add", func(a, b int) int { return a + b })
cobra.AddTemplateFunc("hasAliases", hasAliases)
@ -69,20 +73,20 @@ func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *c
}
}
return opts, helpCommand
return opts, flags, helpCommand
}
// SetupRootCommand sets default usage, help, and error handling for the
// root command.
func SetupRootCommand(rootCmd *cobra.Command) (opts *cliflags.ClientOptions, helpCmd *cobra.Command) {
func SetupRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *pflag.FlagSet, *cobra.Command) {
rootCmd.SetVersionTemplate("Docker version {{.Version}}\n")
return setupCommonRootCommand(rootCmd)
}
// SetupPluginRootCommand sets default usage, help and error handling for a plugin root command.
func SetupPluginRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *pflag.FlagSet) {
opts, _ := setupCommonRootCommand(rootCmd)
return opts, rootCmd.Flags()
opts, flags, _ := setupCommonRootCommand(rootCmd)
return opts, flags
}
// FlagErrorFunc prints an error message which matches the format of the
@ -176,7 +180,7 @@ func (tcmd *TopLevelCommand) HandleGlobalFlags() (*cobra.Command, []string, erro
}
// Initialize finalises global option parsing and initializes the docker client.
func (tcmd *TopLevelCommand) Initialize(ops ...command.CLIOption) error {
func (tcmd *TopLevelCommand) Initialize(ops ...command.InitializeOpt) error {
tcmd.opts.SetDefaultOptions(tcmd.flags)
return tcmd.dockerCli.Initialize(tcmd.opts, ops...)
}

View File

@ -30,7 +30,7 @@ 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(dockerCli, options)
if err != nil {
return err
}
@ -58,7 +58,7 @@ const (
allCacheWarning = `WARNING! This will remove all build cache. Are you sure you want to continue?`
)
func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := options.filter.Value()
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
@ -70,7 +70,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
return 0, "", nil
}
report, err := dockerCli.Client().BuildCachePrune(ctx, types.BuildCachePruneOptions{
report, err := dockerCli.Client().BuildCachePrune(context.Background(), types.BuildCachePruneOptions{
All: options.all,
KeepStorage: options.keepStorage.Value(),
Filters: pruneFilters,
@ -93,6 +93,6 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
}
// 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})
func CachePrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
return runPrune(dockerCli, pruneOptions{force: true, all: all, filter: filter})
}

View File

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

View File

@ -7,7 +7,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/checkpoint"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
)
@ -28,7 +28,7 @@ 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(dockerCli, opts)
},
ValidArgsFunction: completion.NoComplete,
}
@ -40,12 +40,16 @@ 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, checkpoint.CreateOptions{
func runCreate(dockerCli command.Cli, opts createOptions) error {
client := dockerCli.Client()
checkpointOpts := types.CheckpointCreateOptions{
CheckpointID: opts.checkpoint,
CheckpointDir: opts.checkpointDir,
Exit: !opts.leaveRunning,
})
}
err := client.CheckpointCreate(context.Background(), opts.container, checkpointOpts)
if err != nil {
return err
}

View File

@ -6,7 +6,7 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/checkpoint"
"github.com/docker/docker/api/types"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -15,7 +15,7 @@ import (
func TestCheckpointCreateErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointCreateFunc func(container string, options checkpoint.CreateOptions) error
checkpointCreateFunc func(container string, options types.CheckpointCreateOptions) error
expectedError string
}{
{
@ -28,7 +28,7 @@ func TestCheckpointCreateErrors(t *testing.T) {
},
{
args: []string{"foo", "bar"},
checkpointCreateFunc: func(container string, options checkpoint.CreateOptions) error {
checkpointCreateFunc: func(container string, options types.CheckpointCreateOptions) error {
return errors.Errorf("error creating checkpoint for container foo")
},
expectedError: "error creating checkpoint for container foo",
@ -50,7 +50,7 @@ func TestCheckpointCreateWithOptions(t *testing.T) {
var containerID, checkpointID, checkpointDir string
var exit bool
cli := test.NewFakeCli(&fakeClient{
checkpointCreateFunc: func(container string, options checkpoint.CreateOptions) error {
checkpointCreateFunc: func(container string, options types.CheckpointCreateOptions) error {
containerID = container
checkpointID = options.CheckpointID
checkpointDir = options.CheckpointDir
@ -59,14 +59,14 @@ func TestCheckpointCreateWithOptions(t *testing.T) {
},
})
cmd := newCreateCommand(cli)
cp := "checkpoint-bar"
cmd.SetArgs([]string{"container-foo", cp})
checkpoint := "checkpoint-bar"
cmd.SetArgs([]string{"container-foo", checkpoint})
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(checkpoint, 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())))
assert.Check(t, is.Equal(checkpoint, strings.TrimSpace(cli.OutBuffer().String())))
}

View File

@ -2,27 +2,29 @@ package checkpoint
import (
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/checkpoint"
"github.com/docker/docker/api/types"
)
const (
defaultCheckpointFormat = "table {{.Name}}"
checkpointNameHeader = "CHECKPOINT NAME"
checkpointNameHeader = "CHECKPOINT NAME"
)
// NewFormat returns a format for use with a checkpoint Context
func NewFormat(source string) formatter.Format {
if source == formatter.TableFormatKey {
switch source {
case formatter.TableFormatKey:
return defaultCheckpointFormat
}
return formatter.Format(source)
}
// FormatWrite writes formatted checkpoints using the Context
func FormatWrite(ctx formatter.Context, checkpoints []checkpoint.Summary) error {
func FormatWrite(ctx formatter.Context, checkpoints []types.Checkpoint) error {
render := func(format func(subContext formatter.SubContext) error) error {
for _, cp := range checkpoints {
if err := format(&checkpointContext{c: cp}); err != nil {
for _, checkpoint := range checkpoints {
if err := format(&checkpointContext{c: checkpoint}); err != nil {
return err
}
}
@ -33,7 +35,7 @@ func FormatWrite(ctx formatter.Context, checkpoints []checkpoint.Summary) error
type checkpointContext struct {
formatter.HeaderContext
c checkpoint.Summary
c types.Checkpoint
}
func newCheckpointContext() *checkpointContext {

View File

@ -5,7 +5,7 @@ import (
"testing"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/checkpoint"
"github.com/docker/docker/api/types"
"gotest.tools/v3/assert"
)
@ -38,14 +38,15 @@ checkpoint-3:
},
}
checkpoints := []types.Checkpoint{
{Name: "checkpoint-1"},
{Name: "checkpoint-2"},
{Name: "checkpoint-3"},
}
for _, testcase := range cases {
out := bytes.NewBufferString("")
testcase.context.Output = out
err := FormatWrite(testcase.context, []checkpoint.Summary{
{Name: "checkpoint-1"},
{Name: "checkpoint-2"},
{Name: "checkpoint-3"},
})
err := FormatWrite(testcase.context, checkpoints)
assert.NilError(t, err)
assert.Equal(t, out.String(), testcase.expected)
}

View File

@ -7,7 +7,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/checkpoint"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
)
@ -24,7 +24,7 @@ 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(dockerCli, args[0], opts)
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
}
@ -35,10 +35,14 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runList(ctx context.Context, dockerCli command.Cli, container string, opts listOptions) error {
checkpoints, err := dockerCli.Client().CheckpointList(ctx, container, checkpoint.ListOptions{
func runList(dockerCli command.Cli, container string, opts listOptions) error {
client := dockerCli.Client()
listOpts := types.CheckpointListOptions{
CheckpointDir: opts.checkpointDir,
})
}
checkpoints, err := client.CheckpointList(context.Background(), container, listOpts)
if err != nil {
return err
}

View File

@ -5,7 +5,7 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/checkpoint"
"github.com/docker/docker/api/types"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -15,7 +15,7 @@ import (
func TestCheckpointListErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointListFunc func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error)
checkpointListFunc func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error)
expectedError string
}{
{
@ -28,8 +28,8 @@ func TestCheckpointListErrors(t *testing.T) {
},
{
args: []string{"foo"},
checkpointListFunc: func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) {
return []checkpoint.Summary{}, errors.Errorf("error getting checkpoints for container foo")
checkpointListFunc: func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
return []types.Checkpoint{}, errors.Errorf("error getting checkpoints for container foo")
},
expectedError: "error getting checkpoints for container foo",
},
@ -49,10 +49,10 @@ func TestCheckpointListErrors(t *testing.T) {
func TestCheckpointListWithOptions(t *testing.T) {
var containerID, checkpointDir string
cli := test.NewFakeCli(&fakeClient{
checkpointListFunc: func(container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) {
checkpointListFunc: func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
containerID = container
checkpointDir = options.CheckpointDir
return []checkpoint.Summary{
return []types.Checkpoint{
{Name: "checkpoint-foo"},
}, nil
},

View File

@ -5,7 +5,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types/checkpoint"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
)
@ -22,7 +22,7 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
Short: "Remove a checkpoint",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return runRemove(cmd.Context(), dockerCli, args[0], args[1], opts)
return runRemove(dockerCli, args[0], args[1], opts)
},
}
@ -32,9 +32,13 @@ 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,
func runRemove(dockerCli command.Cli, container string, checkpoint string, opts removeOptions) error {
client := dockerCli.Client()
removeOpts := types.CheckpointDeleteOptions{
CheckpointID: checkpoint,
CheckpointDir: opts.checkpointDir,
})
}
return client.CheckpointDelete(context.Background(), container, removeOpts)
}

View File

@ -5,7 +5,7 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/checkpoint"
"github.com/docker/docker/api/types"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -14,7 +14,7 @@ import (
func TestCheckpointRemoveErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointDeleteFunc func(container string, options checkpoint.DeleteOptions) error
checkpointDeleteFunc func(container string, options types.CheckpointDeleteOptions) error
expectedError string
}{
{
@ -27,7 +27,7 @@ func TestCheckpointRemoveErrors(t *testing.T) {
},
{
args: []string{"foo", "bar"},
checkpointDeleteFunc: func(container string, options checkpoint.DeleteOptions) error {
checkpointDeleteFunc: func(container string, options types.CheckpointDeleteOptions) error {
return errors.Errorf("error deleting checkpoint")
},
expectedError: "error deleting checkpoint",
@ -48,7 +48,7 @@ func TestCheckpointRemoveErrors(t *testing.T) {
func TestCheckpointRemoveWithOptions(t *testing.T) {
var containerID, checkpointID, checkpointDir string
cli := test.NewFakeCli(&fakeClient{
checkpointDeleteFunc: func(container string, options checkpoint.DeleteOptions) error {
checkpointDeleteFunc: func(container string, options types.CheckpointDeleteOptions) error {
containerID = container
checkpointID = options.CheckpointID
checkpointDir = options.CheckpointDir

View File

@ -1,6 +1,3 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.19
package command
import (
@ -52,7 +49,7 @@ type Cli interface {
Client() client.APIClient
Streams
SetIn(in *streams.In)
Apply(ops ...CLIOption) error
Apply(ops ...DockerCliOption) error
ConfigFile() *configfile.ConfigFile
ServerInfo() ServerInfo
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
@ -85,11 +82,6 @@ type DockerCli struct {
dockerEndpoint docker.Endpoint
contextStoreConfig store.Config
initTimeout time.Duration
// baseCtx is the base context used for internal operations. In the future
// this may be replaced by explicitly passing a context to functions that
// need it.
baseCtx context.Context
}
// DefaultVersion returns api.defaultVersion.
@ -197,13 +189,16 @@ func (cli *DockerCli) ManifestStore() manifeststore.Store {
// 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 ResolveAuthConfig(ctx, cli, index)
}
return registryclient.NewRegistryClient(resolver, UserAgent(), allowInsecure)
}
// InitializeOpt is the type of the functional options passed to DockerCli.Initialize
type InitializeOpt func(dockerCli *DockerCli) error
// 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 {
func WithInitializeClient(makeClient func(dockerCli *DockerCli) (client.APIClient, error)) InitializeOpt {
return func(dockerCli *DockerCli) error {
var err error
dockerCli.client, err = makeClient(dockerCli)
@ -213,7 +208,7 @@ func WithInitializeClient(makeClient func(dockerCli *DockerCli) (client.APIClien
// Initialize the dockerCli runs initialization that must happen after command
// line flags are parsed.
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) error {
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...InitializeOpt) error {
for _, o := range ops {
if err := o(cli); err != nil {
return err
@ -265,15 +260,17 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.
}
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile) (client.APIClient, error) {
opts, err := ep.ClientOpts()
clientOpts, err := ep.ClientOpts()
if err != nil {
return nil, err
}
if len(configFile.HTTPHeaders) > 0 {
opts = append(opts, client.WithHTTPHeaders(configFile.HTTPHeaders))
customHeaders := make(map[string]string, len(configFile.HTTPHeaders))
for k, v := range configFile.HTTPHeaders {
customHeaders[k] = v
}
opts = append(opts, client.WithUserAgent(UserAgent()))
return client.NewClientWithOpts(opts...)
customHeaders["User-Agent"] = UserAgent()
clientOpts = append(clientOpts, client.WithHTTPHeaders(customHeaders))
return client.NewClientWithOpts(clientOpts...)
}
func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint, error) {
@ -328,7 +325,8 @@ func (cli *DockerCli) getInitTimeout() time.Duration {
}
func (cli *DockerCli) initializeFromClient() {
ctx, cancel := context.WithTimeout(cli.baseCtx, cli.getInitTimeout())
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, cli.getInitTimeout())
defer cancel()
ping, err := cli.client.Ping(ctx)
@ -366,7 +364,7 @@ func (cli *DockerCli) ContextStore() store.Store {
// order of preference:
//
// 1. The "--context" command-line option.
// 2. The "DOCKER_CONTEXT" environment variable ([EnvOverrideContext]).
// 2. The "DOCKER_CONTEXT" environment variable.
// 3. The current context as configured through the in "currentContext"
// field in the CLI configuration file ("~/.docker/config.json").
// 4. If no context is configured, use the "default" context.
@ -398,7 +396,7 @@ func (cli *DockerCli) CurrentContext() string {
// occur when trying to use it.
//
// Refer to [DockerCli.CurrentContext] above for further details.
func resolveContextName(opts *cliflags.ClientOptions, cfg *configfile.ConfigFile) string {
func resolveContextName(opts *cliflags.ClientOptions, config *configfile.ConfigFile) string {
if opts != nil && opts.Context != "" {
return opts.Context
}
@ -408,12 +406,12 @@ func resolveContextName(opts *cliflags.ClientOptions, cfg *configfile.ConfigFile
if os.Getenv(client.EnvOverrideHost) != "" {
return DefaultContextName
}
if ctxName := os.Getenv(EnvOverrideContext); ctxName != "" {
if ctxName := os.Getenv("DOCKER_CONTEXT"); ctxName != "" {
return ctxName
}
if cfg != nil && cfg.CurrentContext != "" {
if config != nil && config.CurrentContext != "" {
// We don't validate if this context exists: errors may occur when trying to use it.
return cfg.CurrentContext
return config.CurrentContext
}
return DefaultContextName
}
@ -448,16 +446,13 @@ func (cli *DockerCli) initialize() error {
return
}
}
if cli.baseCtx == nil {
cli.baseCtx = context.Background()
}
cli.initializeFromClient()
})
return cli.initErr
}
// Apply all the operation on the cli
func (cli *DockerCli) Apply(ops ...CLIOption) error {
func (cli *DockerCli) Apply(ops ...DockerCliOption) error {
for _, op := range ops {
if err := op(cli); err != nil {
return err
@ -486,15 +481,15 @@ type ServerInfo struct {
// NewDockerCli returns a DockerCli instance with all operators applied on it.
// It applies by default the standard streams, and the content trust from
// environment.
func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
defaultOps := []CLIOption{
func NewDockerCli(ops ...DockerCliOption) (*DockerCli, error) {
defaultOps := []DockerCliOption{
WithContentTrustFromEnv(),
WithDefaultContextStoreConfig(),
WithStandardStreams(),
}
ops = append(defaultOps, ops...)
cli := &DockerCli{baseCtx: context.Background()}
cli := &DockerCli{}
if err := cli.Apply(ops...); err != nil {
return nil, err
}
@ -521,7 +516,7 @@ func UserAgent() string {
}
var defaultStoreEndpoints = []store.NamedTypeGetter{
store.EndpointTypeGetter(docker.DockerEndpoint, func() any { return &docker.EndpointMeta{} }),
store.EndpointTypeGetter(docker.DockerEndpoint, func() interface{} { return &docker.EndpointMeta{} }),
}
// RegisterDefaultStoreEndpoints registers a new named endpoint
@ -535,7 +530,7 @@ func RegisterDefaultStoreEndpoints(ep ...store.NamedTypeGetter) {
// DefaultContextStoreConfig returns a new store.Config with the default set of endpoints configured.
func DefaultContextStoreConfig() store.Config {
return store.NewConfig(
func() any { return &DockerContext{} },
func() interface{} { return &DockerContext{} },
defaultStoreEndpoints...,
)
}

View File

@ -1,7 +1,6 @@
package command
import (
"context"
"io"
"os"
"strconv"
@ -11,21 +10,11 @@ import (
"github.com/moby/term"
)
// CLIOption applies a modification on a DockerCli.
type CLIOption func(cli *DockerCli) error
// DockerCliOption applies a modification on a DockerCli.
//
// Deprecated: use [CLIOption] instead.
type DockerCliOption = CLIOption
// InitializeOpt is the type of the functional options passed to DockerCli.Initialize
//
// Deprecated: use [CLIOption] instead.
type InitializeOpt = CLIOption
type DockerCliOption func(cli *DockerCli) error
// WithStandardStreams sets a cli in, out and err streams with the standard streams.
func WithStandardStreams() CLIOption {
func WithStandardStreams() DockerCliOption {
return func(cli *DockerCli) error {
// Set terminal emulation based on platform as required.
stdin, stdout, stderr := term.StdStreams()
@ -36,17 +25,8 @@ func WithStandardStreams() CLIOption {
}
}
// WithBaseContext sets the base context of a cli. It is used to propagate
// the context from the command line to the client.
func WithBaseContext(ctx context.Context) CLIOption {
return func(cli *DockerCli) error {
cli.baseCtx = ctx
return nil
}
}
// WithCombinedStreams uses the same stream for the output and error streams.
func WithCombinedStreams(combined io.Writer) CLIOption {
func WithCombinedStreams(combined io.Writer) DockerCliOption {
return func(cli *DockerCli) error {
cli.out = streams.NewOut(combined)
cli.err = combined
@ -55,7 +35,7 @@ func WithCombinedStreams(combined io.Writer) CLIOption {
}
// WithInputStream sets a cli input stream.
func WithInputStream(in io.ReadCloser) CLIOption {
func WithInputStream(in io.ReadCloser) DockerCliOption {
return func(cli *DockerCli) error {
cli.in = streams.NewIn(in)
return nil
@ -63,7 +43,7 @@ func WithInputStream(in io.ReadCloser) CLIOption {
}
// WithOutputStream sets a cli output stream.
func WithOutputStream(out io.Writer) CLIOption {
func WithOutputStream(out io.Writer) DockerCliOption {
return func(cli *DockerCli) error {
cli.out = streams.NewOut(out)
return nil
@ -71,7 +51,7 @@ func WithOutputStream(out io.Writer) CLIOption {
}
// WithErrorStream sets a cli error stream.
func WithErrorStream(err io.Writer) CLIOption {
func WithErrorStream(err io.Writer) DockerCliOption {
return func(cli *DockerCli) error {
cli.err = err
return nil
@ -79,7 +59,7 @@ func WithErrorStream(err io.Writer) CLIOption {
}
// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
func WithContentTrustFromEnv() CLIOption {
func WithContentTrustFromEnv() DockerCliOption {
return func(cli *DockerCli) error {
cli.contentTrust = false
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
@ -93,7 +73,7 @@ func WithContentTrustFromEnv() CLIOption {
}
// WithContentTrust enables content trust on a cli.
func WithContentTrust(enabled bool) CLIOption {
func WithContentTrust(enabled bool) DockerCliOption {
return func(cli *DockerCli) error {
cli.contentTrust = enabled
return nil
@ -101,7 +81,7 @@ func WithContentTrust(enabled bool) CLIOption {
}
// WithDefaultContextStoreConfig configures the cli to use the default context store configuration.
func WithDefaultContextStoreConfig() CLIOption {
func WithDefaultContextStoreConfig() DockerCliOption {
return func(cli *DockerCli) error {
cli.contextStoreConfig = DefaultContextStoreConfig()
return nil
@ -109,7 +89,7 @@ func WithDefaultContextStoreConfig() CLIOption {
}
// WithAPIClient configures the cli to use the given API client.
func WithAPIClient(c client.APIClient) CLIOption {
func WithAPIClient(c client.APIClient) DockerCliOption {
return func(cli *DockerCli) error {
cli.client = c
return nil

View File

@ -8,7 +8,6 @@ import (
)
func contentTrustEnabled(t *testing.T) bool {
t.Helper()
var cli DockerCli
assert.NilError(t, WithContentTrustFromEnv()(&cli))
return cli.contentTrust

View File

@ -6,7 +6,6 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/volume"
"github.com/spf13/cobra"
)
@ -34,7 +33,7 @@ func ImageNames(dockerCli command.Cli) ValidArgsFn {
// Set DOCKER_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs.
func ContainerNames(dockerCli command.Cli, all bool, filters ...func(types.Container) bool) ValidArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCli.Client().ContainerList(cmd.Context(), container.ListOptions{
list, err := dockerCli.Client().ContainerList(cmd.Context(), types.ContainerListOptions{
All: all,
})
if err != nil {

View File

@ -35,7 +35,7 @@ func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
createOpts.Name = args[0]
createOpts.File = args[1]
return RunConfigCreate(cmd.Context(), dockerCli, createOpts)
return RunConfigCreate(dockerCli, createOpts)
},
ValidArgsFunction: completion.NoComplete,
}
@ -48,8 +48,9 @@ func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command {
}
// RunConfigCreate creates a config with the given options.
func RunConfigCreate(ctx context.Context, dockerCli command.Cli, options CreateOptions) error {
func RunConfigCreate(dockerCli command.Cli, options CreateOptions) error {
client := dockerCli.Client()
ctx := context.Background()
var in io.Reader = dockerCli.In()
if options.File != "-" {

View File

@ -101,9 +101,9 @@ func (c *configContext) Labels() string {
if mapLabels == nil {
return ""
}
joinLabels := make([]string, 0, len(mapLabels))
var joinLabels []string
for k, v := range mapLabels {
joinLabels = append(joinLabels, k+"="+v)
joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
}
return strings.Join(joinLabels, ",")
}

View File

@ -1,6 +1,3 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.19
package config
import (
@ -30,7 +27,7 @@ func newConfigInspectCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.Names = args
return RunConfigInspect(cmd.Context(), dockerCli, opts)
return RunConfigInspect(dockerCli, opts)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completeNames(dockerCli)(cmd, args, toComplete)
@ -43,14 +40,15 @@ func newConfigInspectCommand(dockerCli command.Cli) *cobra.Command {
}
// RunConfigInspect inspects the given Swarm config.
func RunConfigInspect(ctx context.Context, dockerCli command.Cli, opts InspectOptions) error {
func RunConfigInspect(dockerCli command.Cli, opts InspectOptions) error {
client := dockerCli.Client()
ctx := context.Background()
if opts.Pretty {
opts.Format = "pretty"
}
getRef := func(id string) (any, []byte, error) {
getRef := func(id string) (interface{}, []byte, error) {
return client.ConfigInspectWithRaw(ctx, id)
}
f := opts.Format

View File

@ -8,7 +8,7 @@ import (
"time"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
. "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
@ -43,7 +43,7 @@ func TestConfigInspectErrors(t *testing.T) {
args: []string{"foo", "bar"},
configInspectFunc: func(_ context.Context, configID string) (swarm.Config, []byte, error) {
if configID == "foo" {
return *builders.Config(builders.ConfigName("foo")), nil, nil
return *Config(ConfigName("foo")), nil, nil
}
return swarm.Config{}, nil, errors.Errorf("error while inspecting the config")
},
@ -58,7 +58,7 @@ func TestConfigInspectErrors(t *testing.T) {
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
assert.Check(t, cmd.Flags().Set(key, value))
cmd.Flags().Set(key, value)
}
cmd.SetOut(io.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -78,14 +78,14 @@ func TestConfigInspectWithoutFormat(t *testing.T) {
if name != "foo" {
return swarm.Config{}, nil, errors.Errorf("Invalid name, expected %s, got %s", "foo", name)
}
return *builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")), nil, nil
return *Config(ConfigID("ID-foo"), ConfigName("foo")), nil, nil
},
},
{
name: "multiple-configs-with-labels",
args: []string{"foo", "bar"},
configInspectFunc: func(_ context.Context, name string) (swarm.Config, []byte, error) {
return *builders.Config(builders.ConfigID("ID-"+name), builders.ConfigName(name), builders.ConfigLabels(map[string]string{
return *Config(ConfigID("ID-"+name), ConfigName(name), ConfigLabels(map[string]string{
"label1": "label-foo",
})), nil, nil
},
@ -102,7 +102,7 @@ func TestConfigInspectWithoutFormat(t *testing.T) {
func TestConfigInspectWithFormat(t *testing.T) {
configInspectFunc := func(_ context.Context, name string) (swarm.Config, []byte, error) {
return *builders.Config(builders.ConfigName("foo"), builders.ConfigLabels(map[string]string{
return *Config(ConfigName("foo"), ConfigLabels(map[string]string{
"label1": "label-foo",
})), nil, nil
}
@ -131,7 +131,7 @@ func TestConfigInspectWithFormat(t *testing.T) {
})
cmd := newConfigInspectCommand(cli)
cmd.SetArgs(tc.args)
assert.Check(t, cmd.Flags().Set("format", tc.format))
cmd.Flags().Set("format", tc.format)
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("config-inspect-with-format.%s.golden", tc.name))
}
@ -145,15 +145,15 @@ func TestConfigInspectPretty(t *testing.T) {
{
name: "simple",
configInspectFunc: func(_ context.Context, id string) (swarm.Config, []byte, error) {
return *builders.Config(
builders.ConfigLabels(map[string]string{
return *Config(
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")),
ConfigID("configID"),
ConfigName("configName"),
ConfigCreatedAt(time.Time{}),
ConfigUpdatedAt(time.Time{}),
ConfigData([]byte("payload here")),
), []byte{}, nil
},
},
@ -165,7 +165,7 @@ func TestConfigInspectPretty(t *testing.T) {
cmd := newConfigInspectCommand(cli)
cmd.SetArgs([]string{"configID"})
assert.Check(t, cmd.Flags().Set("pretty", "true"))
cmd.Flags().Set("pretty", "true")
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("config-inspect-pretty.%s.golden", tc.name))
}

View File

@ -31,7 +31,7 @@ func newConfigListCommand(dockerCli command.Cli) *cobra.Command {
Short: "List configs",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return RunConfigList(cmd.Context(), dockerCli, listOpts)
return RunConfigList(dockerCli, listOpts)
},
ValidArgsFunction: completion.NoComplete,
}
@ -45,8 +45,9 @@ func newConfigListCommand(dockerCli command.Cli) *cobra.Command {
}
// RunConfigList lists Swarm configs.
func RunConfigList(ctx context.Context, dockerCli command.Cli, options ListOptions) error {
func RunConfigList(dockerCli command.Cli, options ListOptions) error {
client := dockerCli.Client()
ctx := context.Background()
configs, err := client.ConfigList(ctx, types.ConfigListOptions{Filters: options.Filter.Value()})
if err != nil {

View File

@ -8,7 +8,7 @@ import (
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
. "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"
@ -50,23 +50,23 @@ func TestConfigList(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
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)),
*Config(ConfigID("ID-1-foo"),
ConfigName("1-foo"),
ConfigVersion(swarm.Version{Index: 10}),
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
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)),
*Config(ConfigID("ID-10-foo"),
ConfigName("10-foo"),
ConfigVersion(swarm.Version{Index: 11}),
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
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)),
*Config(ConfigID("ID-2-foo"),
ConfigName("2-foo"),
ConfigVersion(swarm.Version{Index: 11}),
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
}, nil
},
@ -80,15 +80,15 @@ func TestConfigListWithQuietOption(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
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{
*Config(ConfigID("ID-foo"), ConfigName("foo")),
*Config(ConfigID("ID-bar"), ConfigName("bar"), ConfigLabels(map[string]string{
"label": "label-bar",
})),
}, nil
},
})
cmd := newConfigListCommand(cli)
assert.Check(t, cmd.Flags().Set("quiet", "true"))
cmd.Flags().Set("quiet", "true")
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "config-list-with-quiet-option.golden")
}
@ -97,8 +97,8 @@ func TestConfigListWithConfigFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
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{
*Config(ConfigID("ID-foo"), ConfigName("foo")),
*Config(ConfigID("ID-bar"), ConfigName("bar"), ConfigLabels(map[string]string{
"label": "label-bar",
})),
}, nil
@ -116,15 +116,15 @@ func TestConfigListWithFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
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{
*Config(ConfigID("ID-foo"), ConfigName("foo")),
*Config(ConfigID("ID-bar"), ConfigName("bar"), ConfigLabels(map[string]string{
"label": "label-bar",
})),
}, nil
},
})
cmd := newConfigListCommand(cli)
assert.Check(t, cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}"))
cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}")
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "config-list-with-format.golden")
}
@ -135,24 +135,24 @@ func TestConfigListWithFilter(t *testing.T) {
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)),
*Config(ConfigID("ID-foo"),
ConfigName("foo"),
ConfigVersion(swarm.Version{Index: 10}),
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
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)),
*Config(ConfigID("ID-bar"),
ConfigName("bar"),
ConfigVersion(swarm.Version{Index: 11}),
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
}, nil
},
})
cmd := newConfigListCommand(cli)
assert.Check(t, cmd.Flags().Set("filter", "name=foo"))
assert.Check(t, cmd.Flags().Set("filter", "label=lbl1=Label-bar"))
cmd.Flags().Set("filter", "name=foo")
cmd.Flags().Set("filter", "label=lbl1=Label-bar")
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "config-list-with-filter.golden")
}

View File

@ -26,7 +26,7 @@ func newConfigRemoveCommand(dockerCli command.Cli) *cobra.Command {
opts := RemoveOptions{
Names: args,
}
return RunConfigRemove(cmd.Context(), dockerCli, opts)
return RunConfigRemove(dockerCli, opts)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completeNames(dockerCli)(cmd, args, toComplete)
@ -35,8 +35,9 @@ func newConfigRemoveCommand(dockerCli command.Cli) *cobra.Command {
}
// RunConfigRemove removes the given Swarm configs.
func RunConfigRemove(ctx context.Context, dockerCli command.Cli, opts RemoveOptions) error {
func RunConfigRemove(dockerCli command.Cli, opts RemoveOptions) error {
client := dockerCli.Client()
ctx := context.Background()
var errs []string

View File

@ -17,15 +17,16 @@ import (
"github.com/spf13/cobra"
)
// AttachOptions group options for `attach` command
type AttachOptions struct {
NoStdin bool
Proxy bool
DetachKeys string
type attachOptions struct {
noStdin bool
proxy bool
detachKeys string
container string
}
func inspectContainerAndCheckState(ctx context.Context, apiClient client.APIClient, args string) (*types.ContainerJSON, error) {
c, err := apiClient.ContainerInspect(ctx, args)
func inspectContainerAndCheckState(ctx context.Context, cli client.APIClient, args string) (*types.ContainerJSON, error) {
c, err := cli.ContainerInspect(ctx, args)
if err != nil {
return nil, err
}
@ -44,73 +45,71 @@ func inspectContainerAndCheckState(ctx context.Context, apiClient client.APIClie
// NewAttachCommand creates a new cobra.Command for `docker attach`
func NewAttachCommand(dockerCli command.Cli) *cobra.Command {
var opts AttachOptions
var ctr string
var opts attachOptions
cmd := &cobra.Command{
Use: "attach [OPTIONS] CONTAINER",
Short: "Attach local standard input, output, and error streams to a running container",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctr = args[0]
return RunAttach(cmd.Context(), dockerCli, ctr, &opts)
opts.container = args[0]
return runAttach(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container attach, docker attach",
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(ctr types.Container) bool {
return ctr.State != "paused"
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(container types.Container) bool {
return container.State != "paused"
}),
}
flags := cmd.Flags()
flags.BoolVar(&opts.NoStdin, "no-stdin", false, "Do not attach STDIN")
flags.BoolVar(&opts.Proxy, "sig-proxy", true, "Proxy all received signals to the process")
flags.StringVar(&opts.DetachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
flags.BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN")
flags.BoolVar(&opts.proxy, "sig-proxy", true, "Proxy all received signals to the process")
flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
return cmd
}
// RunAttach executes an `attach` command
func RunAttach(ctx context.Context, dockerCLI command.Cli, target string, opts *AttachOptions) error {
apiClient := dockerCLI.Client()
func runAttach(dockerCli command.Cli, opts *attachOptions) error {
ctx := context.Background()
client := dockerCli.Client()
// request channel to wait for client
resultC, errC := apiClient.ContainerWait(ctx, target, "")
resultC, errC := client.ContainerWait(ctx, opts.container, "")
c, err := inspectContainerAndCheckState(ctx, apiClient, target)
c, err := inspectContainerAndCheckState(ctx, client, opts.container)
if err != nil {
return err
}
if err := dockerCLI.In().CheckTty(!opts.NoStdin, c.Config.Tty); err != nil {
if err := dockerCli.In().CheckTty(!opts.noStdin, c.Config.Tty); err != nil {
return err
}
detachKeys := dockerCLI.ConfigFile().DetachKeys
if opts.DetachKeys != "" {
detachKeys = opts.DetachKeys
if opts.detachKeys != "" {
dockerCli.ConfigFile().DetachKeys = opts.detachKeys
}
options := container.AttachOptions{
options := types.ContainerAttachOptions{
Stream: true,
Stdin: !opts.NoStdin && c.Config.OpenStdin,
Stdin: !opts.noStdin && c.Config.OpenStdin,
Stdout: true,
Stderr: true,
DetachKeys: detachKeys,
DetachKeys: dockerCli.ConfigFile().DetachKeys,
}
var in io.ReadCloser
if options.Stdin {
in = dockerCLI.In()
in = dockerCli.In()
}
if opts.Proxy && !c.Config.Tty {
if opts.proxy && !c.Config.Tty {
sigc := notifyAllSignals()
go ForwardAllSignals(ctx, apiClient, target, sigc)
go ForwardAllSignals(ctx, dockerCli, opts.container, sigc)
defer signal.StopCatch(sigc)
}
resp, errAttach := apiClient.ContainerAttach(ctx, target, options)
resp, errAttach := client.ContainerAttach(ctx, opts.container, options)
if errAttach != nil {
return errAttach
}
@ -124,20 +123,20 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, target string, opts *
// the container and not exit.
//
// Recheck the container's state to avoid attach block.
_, err = inspectContainerAndCheckState(ctx, apiClient, target)
_, err = inspectContainerAndCheckState(ctx, client, opts.container)
if err != nil {
return err
}
if c.Config.Tty && dockerCLI.Out().IsTerminal() {
resizeTTY(ctx, dockerCLI, target)
if c.Config.Tty && dockerCli.Out().IsTerminal() {
resizeTTY(ctx, dockerCli, opts.container)
}
streamer := hijackedIOStreamer{
streams: dockerCLI,
streams: dockerCli,
inputStream: in,
outputStream: dockerCLI.Out(),
errorStream: dockerCLI.Err(),
outputStream: dockerCli.Out(),
errorStream: dockerCli.Err(),
resp: resp,
tty: c.Config.Tty,
detachKeys: options.DetachKeys,

View File

@ -7,7 +7,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/client"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
@ -16,28 +15,28 @@ type fakeClient struct {
client.Client
inspectFunc func(string) (types.ContainerJSON, error)
execInspectFunc func(execID string) (types.ContainerExecInspect, error)
execCreateFunc func(containerID string, config types.ExecConfig) (types.IDResponse, error)
execCreateFunc func(container string, config types.ExecConfig) (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
containerStartFunc func(container string, options types.ContainerStartOptions) error
imageCreateFunc func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
infoFunc func() (system.Info, error)
containerStatPathFunc func(containerID, path string) (types.ContainerPathStat, error)
containerCopyFromFunc func(containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
logFunc func(string, container.LogsOptions) (io.ReadCloser, error)
infoFunc func() (types.Info, error)
containerStatPathFunc func(container, path string) (types.ContainerPathStat, error)
containerCopyFromFunc func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
logFunc func(string, types.ContainerLogsOptions) (io.ReadCloser, error)
waitFunc func(string) (<-chan container.WaitResponse, <-chan error)
containerListFunc func(container.ListOptions) ([]types.Container, error)
containerListFunc func(types.ContainerListOptions) ([]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
containerExecResizeFunc func(id string, options types.ResizeOptions) error
containerRemoveFunc func(ctx context.Context, container string, options types.ContainerRemoveOptions) error
containerKillFunc func(ctx context.Context, container, signal string) error
Version string
}
func (f *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]types.Container, error) {
func (f *fakeClient) ContainerList(_ context.Context, options types.ContainerListOptions) ([]types.Container, error) {
if f.containerListFunc != nil {
return f.containerListFunc(options)
}
@ -51,9 +50,9 @@ func (f *fakeClient) ContainerInspect(_ context.Context, containerID string) (ty
return types.ContainerJSON{}, nil
}
func (f *fakeClient) ContainerExecCreate(_ context.Context, containerID string, config types.ExecConfig) (types.IDResponse, error) {
func (f *fakeClient) ContainerExecCreate(_ context.Context, container string, config types.ExecConfig) (types.IDResponse, error) {
if f.execCreateFunc != nil {
return f.execCreateFunc(containerID, config)
return f.execCreateFunc(container, config)
}
return types.IDResponse{}, nil
}
@ -83,9 +82,9 @@ func (f *fakeClient) ContainerCreate(
return container.CreateResponse{}, nil
}
func (f *fakeClient) ContainerRemove(ctx context.Context, containerID string, options container.RemoveOptions) error {
func (f *fakeClient) ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error {
if f.containerRemoveFunc != nil {
return f.containerRemoveFunc(ctx, containerID, options)
return f.containerRemoveFunc(ctx, container, options)
}
return nil
}
@ -97,30 +96,30 @@ func (f *fakeClient) ImageCreate(_ context.Context, parentReference string, opti
return nil, nil
}
func (f *fakeClient) Info(_ context.Context) (system.Info, error) {
func (f *fakeClient) Info(_ context.Context) (types.Info, error) {
if f.infoFunc != nil {
return f.infoFunc()
}
return system.Info{}, nil
return types.Info{}, nil
}
func (f *fakeClient) ContainerStatPath(_ context.Context, containerID, path string) (types.ContainerPathStat, error) {
func (f *fakeClient) ContainerStatPath(_ context.Context, container, path string) (types.ContainerPathStat, error) {
if f.containerStatPathFunc != nil {
return f.containerStatPathFunc(containerID, path)
return f.containerStatPathFunc(container, path)
}
return types.ContainerPathStat{}, nil
}
func (f *fakeClient) CopyFromContainer(_ context.Context, containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
func (f *fakeClient) CopyFromContainer(_ context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
if f.containerCopyFromFunc != nil {
return f.containerCopyFromFunc(containerID, srcPath)
return f.containerCopyFromFunc(container, srcPath)
}
return nil, types.ContainerPathStat{}, nil
}
func (f *fakeClient) ContainerLogs(_ context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
func (f *fakeClient) ContainerLogs(_ context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) {
if f.logFunc != nil {
return f.logFunc(containerID, options)
return f.logFunc(container, options)
}
return nil, nil
}
@ -129,37 +128,37 @@ func (f *fakeClient) ClientVersion() string {
return f.Version
}
func (f *fakeClient) ContainerWait(_ context.Context, containerID string, _ container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
func (f *fakeClient) ContainerWait(_ context.Context, container string, _ container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
if f.waitFunc != nil {
return f.waitFunc(containerID)
return f.waitFunc(container)
}
return nil, nil
}
func (f *fakeClient) ContainerStart(_ context.Context, containerID string, options container.StartOptions) error {
func (f *fakeClient) ContainerStart(_ context.Context, container string, options types.ContainerStartOptions) error {
if f.containerStartFunc != nil {
return f.containerStartFunc(containerID, options)
return f.containerStartFunc(container, options)
}
return nil
}
func (f *fakeClient) ContainerExport(_ context.Context, containerID string) (io.ReadCloser, error) {
func (f *fakeClient) ContainerExport(_ context.Context, container string) (io.ReadCloser, error) {
if f.containerExportFunc != nil {
return f.containerExportFunc(containerID)
return f.containerExportFunc(container)
}
return nil, nil
}
func (f *fakeClient) ContainerExecResize(_ context.Context, id string, options container.ResizeOptions) error {
func (f *fakeClient) ContainerExecResize(_ context.Context, id string, options types.ResizeOptions) error {
if f.containerExecResizeFunc != nil {
return f.containerExecResizeFunc(id, options)
}
return nil
}
func (f *fakeClient) ContainerKill(ctx context.Context, containerID, signal string) error {
func (f *fakeClient) ContainerKill(ctx context.Context, container, signal string) error {
if f.containerKillFunc != nil {
return f.containerKillFunc(ctx, containerID, signal)
return f.containerKillFunc(ctx, container, signal)
}
return nil
}

View File

@ -8,7 +8,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
)
@ -35,7 +35,7 @@ func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
if len(args) > 1 {
options.reference = args[1]
}
return runCommit(cmd.Context(), dockerCli, &options)
return runCommit(dockerCli, &options)
},
Annotations: map[string]string{
"aliases": "docker container commit, docker commit",
@ -56,14 +56,21 @@ func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runCommit(ctx context.Context, dockerCli command.Cli, options *commitOptions) error {
response, err := dockerCli.Client().ContainerCommit(ctx, options.container, container.CommitOptions{
Reference: options.reference,
func runCommit(dockerCli command.Cli, options *commitOptions) error {
ctx := context.Background()
name := options.container
reference := options.reference
commitOptions := types.ContainerCommitOptions{
Reference: reference,
Comment: options.comment,
Author: options.author,
Changes: options.changes.GetAll(),
Pause: options.pause,
})
}
response, err := dockerCli.Client().ContainerCommit(ctx, name, commitOptions)
if err != nil {
return err
}

View File

@ -151,7 +151,7 @@ func NewCopyCommand(dockerCli command.Cli) *cobra.Command {
// User did not specify "quiet" flag; suppress output if no terminal is attached
opts.quiet = !dockerCli.Out().IsTerminal()
}
return runCopy(cmd.Context(), dockerCli, opts)
return runCopy(dockerCli, opts)
},
Annotations: map[string]string{
"aliases": "docker container cp, docker cp",
@ -169,7 +169,7 @@ func progressHumanSize(n int64) string {
return units.HumanSizeWithPrecision(float64(n), 3)
}
func runCopy(ctx context.Context, dockerCli command.Cli, opts copyOptions) error {
func runCopy(dockerCli command.Cli, opts copyOptions) error {
srcContainer, srcPath := splitCpArg(opts.source)
destContainer, destPath := splitCpArg(opts.destination)
@ -191,6 +191,8 @@ func runCopy(ctx context.Context, dockerCli command.Cli, opts copyOptions) error
copyConfig.container = destContainer
}
ctx := context.Background()
switch direction {
case fromContainer:
return copyFromContainer(ctx, dockerCli, copyConfig)
@ -244,6 +246,7 @@ func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cp
linkTarget, rebaseName = archive.GetRebaseName(srcPath, linkTarget)
srcPath = linkTarget
}
}
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)

View File

@ -1,7 +1,6 @@
package container
import (
"context"
"io"
"os"
"runtime"
@ -42,7 +41,7 @@ func TestRunCopyWithInvalidArguments(t *testing.T) {
}
for _, testcase := range testcases {
t.Run(testcase.doc, func(t *testing.T) {
err := runCopy(context.TODO(), test.NewFakeCli(nil), testcase.options)
err := runCopy(test.NewFakeCli(nil), testcase.options)
assert.Error(t, err, testcase.expectedErr)
})
}
@ -59,7 +58,7 @@ func TestRunCopyFromContainerToStdout(t *testing.T) {
}
options := copyOptions{source: "container:/path", destination: "-"}
cli := test.NewFakeCli(fakeClient)
err := runCopy(context.TODO(), cli, options)
err := runCopy(cli, options)
assert.NilError(t, err)
assert.Check(t, is.Equal(tarContent, cli.OutBuffer().String()))
assert.Check(t, is.Equal("", cli.ErrBuffer().String()))
@ -79,7 +78,7 @@ func TestRunCopyFromContainerToFilesystem(t *testing.T) {
}
options := copyOptions{source: "container:/path", destination: destDir.Path(), quiet: true}
cli := test.NewFakeCli(fakeClient)
err := runCopy(context.TODO(), cli, options)
err := runCopy(cli, options)
assert.NilError(t, err)
assert.Check(t, is.Equal("", cli.OutBuffer().String()))
assert.Check(t, is.Equal("", cli.ErrBuffer().String()))
@ -107,7 +106,7 @@ func TestRunCopyFromContainerToFilesystemMissingDestinationDirectory(t *testing.
destination: destDir.Join("missing", "foo"),
}
cli := test.NewFakeCli(fakeClient)
err := runCopy(context.TODO(), cli, options)
err := runCopy(cli, options)
assert.ErrorContains(t, err, destDir.Join("missing"))
}
@ -120,7 +119,7 @@ func TestRunCopyToContainerFromFileWithTrailingSlash(t *testing.T) {
destination: "container:/path",
}
cli := test.NewFakeCli(&fakeClient{})
err := runCopy(context.TODO(), cli, options)
err := runCopy(cli, options)
expectedError := "not a directory"
if runtime.GOOS == "windows" {
@ -135,7 +134,7 @@ func TestRunCopyToContainerSourceDoesNotExist(t *testing.T) {
destination: "container:/path",
}
cli := test.NewFakeCli(&fakeClient{})
err := runCopy(context.TODO(), cli, options)
err := runCopy(cli, options)
expected := "no such file or directory"
if runtime.GOOS == "windows" {
expected = "cannot find the file specified"
@ -194,7 +193,7 @@ func TestSplitCpArg(t *testing.T) {
func TestRunCopyFromContainerToFilesystemIrregularDestination(t *testing.T) {
options := copyOptions{source: "container:/dev/null", destination: "/dev/random"}
cli := test.NewFakeCli(nil)
err := runCopy(context.TODO(), cli, options)
err := runCopy(cli, options)
assert.Assert(t, err != nil)
expected := `"/dev/random" must be a directory or a regular file`
assert.ErrorContains(t, err, expected)

View File

@ -8,17 +8,17 @@ import (
"regexp"
"github.com/containerd/containerd/platforms"
"github.com/distribution/reference"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/opts"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/errdefs"
apiclient "github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
@ -55,7 +55,7 @@ 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(dockerCli, cmd.Flags(), &options, copts)
},
Annotations: map[string]string{
"aliases": "docker container create, docker create",
@ -80,7 +80,7 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error {
func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error {
if err := validatePullOpt(options.pull); err != nil {
reportError(dockerCli.Err(), "create", err.Error(), true)
return cli.StatusError{StatusCode: 125}
@ -91,7 +91,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
if v == nil {
newEnv = append(newEnv, k)
} else {
newEnv = append(newEnv, k+"="+*v)
newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v))
}
}
copts.env = *opts.NewListOptsRef(&newEnv, nil)
@ -104,24 +104,24 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
reportError(dockerCli.Err(), "create", err.Error(), true)
return cli.StatusError{StatusCode: 125}
}
id, err := createContainer(ctx, dockerCli, containerCfg, options)
response, err := createContainer(context.Background(), dockerCli, containerCfg, options)
if err != nil {
return err
}
_, _ = fmt.Fprintln(dockerCli.Out(), id)
fmt.Fprintln(dockerCli.Out(), response.ID)
return nil
}
// FIXME(thaJeztah): this is the only code-path that uses APIClient.ImageCreate. Rewrite this to use the regular "pull" code (or vice-versa).
func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *createOptions) error {
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), img)
func pullImage(ctx context.Context, dockerCli command.Cli, image string, opts *createOptions) error {
encodedAuth, err := command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
if err != nil {
return err
}
responseBody, err := dockerCli.Client().ImageCreate(ctx, img, types.ImageCreateOptions{
responseBody, err := dockerCli.Client().ImageCreate(ctx, image, types.ImageCreateOptions{
RegistryAuth: encodedAuth,
Platform: options.platform,
Platform: opts.platform,
})
if err != nil {
return err
@ -129,7 +129,7 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
defer responseBody.Close()
out := dockerCli.Err()
if options.quiet {
if opts.quiet {
out = io.Discard
}
return jsonmessage.DisplayJSONMessagesToStream(responseBody, streams.NewOut(out), nil)
@ -185,7 +185,7 @@ func newCIDFile(path string) (*cidFile, error) {
}
//nolint:gocyclo
func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *containerConfig, options *createOptions) (containerID string, err error) {
func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *containerConfig, opts *createOptions) (*container.CreateResponse, error) {
config := containerCfg.Config
hostConfig := containerCfg.HostConfig
networkingConfig := containerCfg.NetworkingConfig
@ -200,29 +200,29 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
containerIDFile, err := newCIDFile(hostConfig.ContainerIDFile)
if err != nil {
return "", err
return nil, err
}
defer containerIDFile.Close()
ref, err := reference.ParseAnyReference(config.Image)
if err != nil {
return "", err
return nil, err
}
if named, ok := ref.(reference.Named); ok {
namedRef = reference.TagNameOnly(named)
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && !options.untrusted {
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && !opts.untrusted {
var err error
trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef)
if err != nil {
return "", err
return nil, err
}
config.Image = reference.FamiliarString(trustedRef)
}
}
pullAndTagImage := func() error {
if err := pullImage(ctx, dockerCli, config.Image, options); err != nil {
if err := pullImage(ctx, dockerCli, config.Image, opts); err != nil {
return err
}
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
@ -236,50 +236,50 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
// 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 opts.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") {
p, err := platforms.Parse(opts.platform)
if err != nil {
return "", errors.Wrap(err, "error parsing specified platform")
return nil, errors.Wrap(err, "error parsing specified platform")
}
platform = &p
}
if options.pull == PullImageAlways {
if opts.pull == PullImageAlways {
if err := pullAndTagImage(); err != nil {
return "", err
return nil, err
}
}
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name)
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, opts.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 {
if apiclient.IsErrNotFound(err) && namedRef != nil && opts.pull == PullImageMissing {
if !opts.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))
}
if err := pullAndTagImage(); err != nil {
return "", err
return nil, err
}
var retryErr error
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name)
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, opts.name)
if retryErr != nil {
return "", retryErr
return nil, retryErr
}
} else {
return "", err
return nil, err
}
}
for _, w := range response.Warnings {
_, _ = fmt.Fprintf(dockerCli.Err(), "WARNING: %s\n", w)
fmt.Fprintf(dockerCli.Err(), "WARNING: %s\n", w)
}
err = containerIDFile.Write(response.ID)
return response.ID, err
return &response, err
}
func warnOnOomKillDisable(hostConfig container.HostConfig, stderr io.Writer) {

View File

@ -18,7 +18,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/system"
"github.com/google/go-cmp/cmp"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/pflag"
@ -80,10 +79,8 @@ func TestCIDFileCloseWithWrite(t *testing.T) {
}
func TestCreateContainerImagePullPolicy(t *testing.T) {
const (
imageName = "does-not-exist-locally"
containerID = "abcdef"
)
imageName := "does-not-exist-locally"
containerID := "abcdef"
config := &containerConfig{
Config: &container.Config{
Image: imageName,
@ -94,18 +91,18 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
cases := []struct {
PullPolicy string
ExpectedPulls int
ExpectedID string
ExpectedBody container.CreateResponse
ExpectedErrMsg string
ResponseCounter int
}{
{
PullPolicy: PullImageMissing,
ExpectedPulls: 1,
ExpectedID: containerID,
ExpectedBody: container.CreateResponse{ID: containerID},
}, {
PullPolicy: PullImageAlways,
ExpectedPulls: 1,
ExpectedID: containerID,
ExpectedBody: container.CreateResponse{ID: containerID},
ResponseCounter: 1, // This lets us return a container on the first pull
}, {
PullPolicy: PullImageNever,
@ -113,52 +110,50 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
ExpectedErrMsg: "error fake not found",
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.PullPolicy, func(t *testing.T) {
pullCounter := 0
for _, c := range cases {
c := c
pullCounter := 0
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 container.CreateResponse{}, fakeNotFound{}
default:
return container.CreateResponse{ID: containerID}, nil
}
},
imageCreateFunc: func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) {
defer func() { pullCounter++ }()
return io.NopCloser(strings.NewReader("")), nil
},
infoFunc: func() (system.Info, error) {
return system.Info{IndexServerAddress: "https://indexserver.example.com"}, nil
},
}
fakeCLI := test.NewFakeCli(client)
id, err := createContainer(context.Background(), fakeCLI, config, &createOptions{
name: "name",
platform: runtime.GOOS,
untrusted: true,
pull: tc.PullPolicy,
})
if tc.ExpectedErrMsg != "" {
assert.Check(t, is.ErrorContains(err, tc.ExpectedErrMsg))
} else {
assert.Check(t, err)
assert.Check(t, is.Equal(tc.ExpectedID, id))
}
assert.Check(t, is.Equal(tc.ExpectedPulls, pullCounter))
client := &fakeClient{
createContainerFunc: func(
config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *specs.Platform,
containerName string,
) (container.CreateResponse, error) {
defer func() { c.ResponseCounter++ }()
switch c.ResponseCounter {
case 0:
return container.CreateResponse{}, fakeNotFound{}
default:
return container.CreateResponse{ID: containerID}, nil
}
},
imageCreateFunc: func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) {
defer func() { pullCounter++ }()
return io.NopCloser(strings.NewReader("")), nil
},
infoFunc: func() (types.Info, error) {
return types.Info{IndexServerAddress: "https://indexserver.example.com"}, nil
},
}
cli := test.NewFakeCli(client)
body, err := createContainer(context.Background(), cli, config, &createOptions{
name: "name",
platform: runtime.GOOS,
untrusted: true,
pull: c.PullPolicy,
})
if c.ExpectedErrMsg != "" {
assert.ErrorContains(t, err, c.ExpectedErrMsg)
} else {
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(c.ExpectedBody, *body))
}
assert.Check(t, is.Equal(c.ExpectedPulls, pullCounter))
}
}
@ -181,7 +176,6 @@ func TestCreateContainerImagePullPolicyInvalid(t *testing.T) {
t.Run(tc.PullPolicy, func(t *testing.T) {
dockerCli := test.NewFakeCli(&fakeClient{})
err := runCreate(
context.TODO(),
dockerCli,
&pflag.FlagSet{},
&createOptions{pull: tc.PullPolicy},
@ -224,7 +218,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
}
for _, tc := range testCases {
tc := tc
fakeCLI := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
@ -234,8 +228,8 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
return container.CreateResponse{}, fmt.Errorf("shouldn't try to pull image")
},
}, test.EnableContentTrust)
fakeCLI.SetNotaryClient(tc.notaryFunc)
cmd := NewCreateCommand(fakeCLI)
cli.SetNotaryClient(tc.notaryFunc)
cmd := NewCreateCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
@ -324,7 +318,7 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
}
sort.Strings(expected)
fakeCLI := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
@ -336,7 +330,7 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
return container.CreateResponse{}, nil
},
})
fakeCLI.SetConfigFile(&configfile.ConfigFile{
cli.SetConfigFile(&configfile.ConfigFile{
Proxies: map[string]configfile.ProxyConfig{
"default": {
HTTPProxy: "httpProxy",
@ -347,7 +341,7 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
},
},
})
cmd := NewCreateCommand(fakeCLI)
cmd := NewCreateCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs([]string{"image:tag"})
err := cmd.Execute()
@ -356,5 +350,5 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
type fakeNotFound struct{}
func (f fakeNotFound) NotFound() {}
func (f fakeNotFound) Error() string { return "error fake not found" }
func (f fakeNotFound) NotFound() bool { return true }
func (f fakeNotFound) Error() string { return "error fake not found" }

View File

@ -25,7 +25,7 @@ func NewDiffCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.container = args[0]
return runDiff(cmd.Context(), dockerCli, &opts)
return runDiff(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container diff, docker diff",
@ -34,10 +34,12 @@ func NewDiffCommand(dockerCli command.Cli) *cobra.Command {
}
}
func runDiff(ctx context.Context, dockerCli command.Cli, opts *diffOptions) error {
func runDiff(dockerCli command.Cli, opts *diffOptions) error {
if opts.container == "" {
return errors.New("Container name cannot be empty")
}
ctx := context.Background()
changes, err := dockerCli.Client().ContainerDiff(ctx, opts.container)
if err != nil {
return err

View File

@ -28,6 +28,7 @@ type ExecOptions struct {
Privileged bool
Env opts.ListOpts
Workdir string
Container string
Command []string
EnvFile opts.ListOpts
}
@ -43,16 +44,15 @@ func NewExecOptions() ExecOptions {
// NewExecCommand creates a new cobra.Command for `docker exec`
func NewExecCommand(dockerCli command.Cli) *cobra.Command {
options := NewExecOptions()
var container string
cmd := &cobra.Command{
Use: "exec [OPTIONS] CONTAINER COMMAND [ARG...]",
Short: "Execute a command in a running container",
Args: cli.RequiresMinArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
container = args[0]
options.Container = args[0]
options.Command = args[1:]
return RunExec(cmd.Context(), dockerCli, container, options)
return RunExec(dockerCli, options)
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(container types.Container) bool {
return container.State != "paused"
@ -96,19 +96,20 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
}
// RunExec executes an `exec` command
func RunExec(ctx context.Context, dockerCli command.Cli, container string, options ExecOptions) error {
func RunExec(dockerCli command.Cli, options ExecOptions) error {
execConfig, err := parseExec(options, dockerCli.ConfigFile())
if err != nil {
return err
}
ctx := context.Background()
client := 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 := client.ContainerInspect(ctx, container); err != nil {
if _, err := client.ContainerInspect(ctx, options.Container); err != nil {
return err
}
if !execConfig.Detach {
@ -119,7 +120,7 @@ func RunExec(ctx context.Context, dockerCli command.Cli, container string, optio
fillConsoleSize(execConfig, dockerCli)
response, err := client.ContainerExecCreate(ctx, container, *execConfig)
response, err := client.ContainerExecCreate(ctx, options.Container, *execConfig)
if err != nil {
return err
}

View File

@ -169,7 +169,8 @@ func TestRunExec(t *testing.T) {
{
doc: "successful detach",
options: withDefaultOpts(ExecOptions{
Detach: true,
Container: "thecontainer",
Detach: true,
}),
client: fakeClient{execCreateFunc: execCreateWithID},
},
@ -192,16 +193,18 @@ func TestRunExec(t *testing.T) {
for _, testcase := range testcases {
t.Run(testcase.doc, func(t *testing.T) {
fakeCLI := test.NewFakeCli(&testcase.client)
cli := test.NewFakeCli(&testcase.client)
err := RunExec(context.TODO(), fakeCLI, "thecontainer", testcase.options)
err := RunExec(cli, testcase.options)
if testcase.expectedError != "" {
assert.ErrorContains(t, err, testcase.expectedError)
} else if !assert.Check(t, err) {
return
} else {
if !assert.Check(t, err) {
return
}
}
assert.Check(t, is.Equal(testcase.expectedOut, fakeCLI.OutBuffer().String()))
assert.Check(t, is.Equal(testcase.expectedErr, fakeCLI.ErrBuffer().String()))
assert.Check(t, is.Equal(testcase.expectedOut, cli.OutBuffer().String()))
assert.Check(t, is.Equal(testcase.expectedErr, cli.ErrBuffer().String()))
})
}
}
@ -262,8 +265,8 @@ func TestNewExecCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
fakeCLI := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})
cmd := NewExecCommand(fakeCLI)
cli := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})
cmd := NewExecCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs(tc.args)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)

View File

@ -26,7 +26,7 @@ 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(dockerCli, opts)
},
Annotations: map[string]string{
"aliases": "docker container export, docker export",
@ -41,7 +41,7 @@ func NewExportCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runExport(ctx context.Context, dockerCli command.Cli, opts exportOptions) error {
func runExport(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")
}
@ -52,7 +52,7 @@ func runExport(ctx context.Context, dockerCli command.Cli, opts exportOptions) e
clnt := dockerCli.Client()
responseBody, err := clnt.ContainerExport(ctx, opts.container)
responseBody, err := clnt.ContainerExport(context.Background(), opts.container)
if err != nil {
return err
}

View File

@ -14,7 +14,8 @@ const (
// NewDiffFormat returns a format for use with a diff Context
func NewDiffFormat(source string) formatter.Format {
if source == formatter.TableFormatKey {
switch source {
case formatter.TableFormatKey:
return defaultDiffTableFormat
}
return formatter.Format(source)

View File

@ -24,7 +24,7 @@ const (
pidsHeader = "PIDS" // Used only on Linux
)
// StatsEntry represents the statistics data collected from a container
// StatsEntry represents represents the statistics data collected from a container
type StatsEntry struct {
Container string
Name string
@ -116,9 +116,9 @@ func NewStats(container string) *Stats {
}
// statsFormatWrite renders the context for a list of containers statistics
func statsFormatWrite(ctx formatter.Context, stats []StatsEntry, osType string, trunc bool) error {
func statsFormatWrite(ctx formatter.Context, Stats []StatsEntry, osType string, trunc bool) error {
render := func(format func(subContext formatter.SubContext) error) error {
for _, cstats := range stats {
for _, cstats := range Stats {
statsCtx := &statsContext{
s: cstats,
os: osType,

View File

@ -87,7 +87,7 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
var restoreOnce sync.Once
restore = func() {
restoreOnce.Do(func() {
_ = restoreTerminal(h.streams, h.inputStream)
restoreTerminal(h.streams, h.inputStream)
})
}

View File

@ -1,6 +1,3 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.19
package container
import (
@ -30,7 +27,7 @@ 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(dockerCli, opts)
},
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
}
@ -42,10 +39,11 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) error {
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
client := dockerCli.Client()
ctx := context.Background()
getRefFunc := func(ref string) (any, []byte, error) {
getRefFunc := func(ref string) (interface{}, []byte, error) {
return client.ContainerInspectWithRaw(ctx, ref, opts.size)
}
return inspect.Inspect(dockerCli.Out(), opts.refs, opts.format, getRefFunc)

View File

@ -28,7 +28,7 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args
return runKill(cmd.Context(), dockerCli, &opts)
return runKill(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container kill, docker kill",
@ -41,8 +41,9 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runKill(ctx context.Context, dockerCli command.Cli, opts *killOptions) error {
func runKill(dockerCli command.Cli, opts *killOptions) error {
var errs []string
ctx := context.Background()
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, container string) error {
return dockerCli.Client().ContainerKill(ctx, container, opts.signal)
})

View File

@ -11,7 +11,7 @@ import (
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
"github.com/docker/cli/templates"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -29,7 +29,7 @@ type psOptions struct {
}
// NewPsCommand creates a new cobra.Command for `docker ps`
func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
func NewPsCommand(dockerCli command.Cli) *cobra.Command {
options := psOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
@ -38,7 +38,7 @@ func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
options.sizeChanged = cmd.Flags().Changed("size")
return runPs(cmd.Context(), dockerCLI, &options)
return runPs(dockerCli, &options)
},
Annotations: map[string]string{
"category-top": "3",
@ -61,28 +61,28 @@ func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
return cmd
}
func newListCommand(dockerCLI command.Cli) *cobra.Command {
cmd := *NewPsCommand(dockerCLI)
func newListCommand(dockerCli command.Cli) *cobra.Command {
cmd := *NewPsCommand(dockerCli)
cmd.Aliases = []string{"ps", "list"}
cmd.Use = "ls [OPTIONS]"
return &cmd
}
func buildContainerListOptions(options *psOptions) (*container.ListOptions, error) {
listOptions := &container.ListOptions{
All: options.all,
Limit: options.last,
Size: options.size,
Filters: options.filter.Value(),
func buildContainerListOptions(opts *psOptions) (*types.ContainerListOptions, error) {
options := &types.ContainerListOptions{
All: opts.all,
Limit: opts.last,
Size: opts.size,
Filters: opts.filter.Value(),
}
if options.nLatest && options.last == -1 {
listOptions.Limit = 1
if opts.nLatest && opts.last == -1 {
options.Limit = 1
}
// always validate template when `--format` is used, for consistency
if len(options.format) > 0 {
tmpl, err := templates.NewParse("", options.format)
if len(opts.format) > 0 {
tmpl, err := templates.NewParse("", opts.format)
if err != nil {
return nil, errors.Wrap(err, "failed to parse template")
}
@ -97,7 +97,7 @@ func buildContainerListOptions(options *psOptions) (*container.ListOptions, erro
// if `size` was not explicitly set to false (with `--size=false`)
// and `--quiet` is not set, request size if the template requires it
if !options.quiet && !listOptions.Size && !options.sizeChanged {
if !opts.quiet && !options.Size && !opts.sizeChanged {
// The --size option isn't set, but .Size may be used in the template.
// Parse and execute the given template to detect if the .Size field is
// used. If it is, then automatically enable the --size option. See #24696
@ -106,20 +106,22 @@ func buildContainerListOptions(options *psOptions) (*container.ListOptions, erro
// because calculating the size is a costly operation.
if _, ok := optionsProcessor.FieldsUsed["Size"]; ok {
listOptions.Size = true
options.Size = true
}
}
}
return listOptions, nil
return options, nil
}
func runPs(ctx context.Context, dockerCLI command.Cli, options *psOptions) error {
func runPs(dockerCli command.Cli, options *psOptions) error {
ctx := context.Background()
if len(options.format) == 0 {
// load custom psFormat from CLI config (if any)
options.format = dockerCLI.ConfigFile().PsFormat
options.format = dockerCli.ConfigFile().PsFormat
} else if options.quiet {
_, _ = dockerCLI.Err().Write([]byte("WARNING: Ignoring custom format, because both --format and --quiet are set.\n"))
_, _ = dockerCli.Err().Write([]byte("WARNING: Ignoring custom format, because both --format and --quiet are set.\n"))
}
listOptions, err := buildContainerListOptions(options)
@ -127,13 +129,13 @@ func runPs(ctx context.Context, dockerCLI command.Cli, options *psOptions) error
return err
}
containers, err := dockerCLI.Client().ContainerList(ctx, *listOptions)
containers, err := dockerCli.Client().ContainerList(ctx, *listOptions)
if err != nil {
return err
}
containerCtx := formatter.Context{
Output: dockerCLI.Out(),
Output: dockerCli.Out(),
Format: formatter.NewContainerFormat(options.format, options.quiet, listOptions.Size),
Trunc: !options.noTrunc,
}

View File

@ -7,10 +7,9 @@ import (
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
. "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
@ -130,7 +129,7 @@ func TestContainerListErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
containerListFunc func(container.ListOptions) ([]types.Container, error)
containerListFunc func(types.ContainerListOptions) ([]types.Container, error)
expectedError string
}{
{
@ -146,7 +145,7 @@ func TestContainerListErrors(t *testing.T) {
expectedError: `wrong number of args for join`,
},
{
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
return nil, fmt.Errorf("error listing containers")
},
expectedError: "error listing containers",
@ -160,7 +159,7 @@ func TestContainerListErrors(t *testing.T) {
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
assert.Check(t, cmd.Flags().Set(key, value))
cmd.Flags().Set(key, value)
}
cmd.SetOut(io.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -169,13 +168,13 @@ func TestContainerListErrors(t *testing.T) {
func TestContainerListWithoutFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
return []types.Container{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo")),
*builders.Container("c3", builders.WithPort(80, 80, builders.TCP), builders.WithPort(81, 81, builders.TCP), builders.WithPort(82, 82, builders.TCP)),
*builders.Container("c4", builders.WithPort(81, 81, builders.UDP)),
*builders.Container("c5", builders.WithPort(82, 82, builders.IP("8.8.8.8"), builders.TCP)),
*Container("c1"),
*Container("c2", WithName("foo")),
*Container("c3", WithPort(80, 80, TCP), WithPort(81, 81, TCP), WithPort(82, 82, TCP)),
*Container("c4", WithPort(81, 81, UDP)),
*Container("c5", WithPort(82, 82, IP("8.8.8.8"), TCP)),
}, nil
},
})
@ -186,15 +185,15 @@ func TestContainerListWithoutFormat(t *testing.T) {
func TestContainerListNoTrunc(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
return []types.Container{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo/bar")),
*Container("c1"),
*Container("c2", WithName("foo/bar")),
}, nil
},
})
cmd := newListCommand(cli)
assert.Check(t, cmd.Flags().Set("no-trunc", "true"))
cmd.Flags().Set("no-trunc", "true")
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "container-list-without-format-no-trunc.golden")
}
@ -202,15 +201,15 @@ func TestContainerListNoTrunc(t *testing.T) {
// Test for GitHub issue docker/docker#21772
func TestContainerListNamesMultipleTime(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
return []types.Container{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo/bar")),
*Container("c1"),
*Container("c2", WithName("foo/bar")),
}, nil
},
})
cmd := newListCommand(cli)
assert.Check(t, cmd.Flags().Set("format", "{{.Names}} {{.Names}}"))
cmd.Flags().Set("format", "{{.Names}} {{.Names}}")
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "container-list-format-name-name.golden")
}
@ -218,15 +217,15 @@ func TestContainerListNamesMultipleTime(t *testing.T) {
// Test for GitHub issue docker/docker#30291
func TestContainerListFormatTemplateWithArg(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
return []types.Container{
*builders.Container("c1", builders.WithLabel("some.label", "value")),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")),
*Container("c1", WithLabel("some.label", "value")),
*Container("c2", WithName("foo/bar"), WithLabel("foo", "bar")),
}, nil
},
})
cmd := newListCommand(cli)
assert.Check(t, cmd.Flags().Set("format", `{{.Names}} {{.Label "some.label"}}`))
cmd.Flags().Set("format", `{{.Names}} {{.Label "some.label"}}`)
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "container-list-format-with-arg.golden")
}
@ -269,15 +268,15 @@ func TestContainerListFormatSizeSetsOption(t *testing.T) {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(options container.ListOptions) ([]types.Container, error) {
containerListFunc: func(options types.ContainerListOptions) ([]types.Container, error) {
assert.Check(t, is.Equal(options.Size, tc.sizeExpected))
return []types.Container{}, nil
},
})
cmd := newListCommand(cli)
assert.Check(t, cmd.Flags().Set("format", tc.format))
cmd.Flags().Set("format", tc.format)
if tc.sizeFlag != "" {
assert.Check(t, cmd.Flags().Set("size", tc.sizeFlag))
cmd.Flags().Set("size", tc.sizeFlag)
}
assert.NilError(t, cmd.Execute())
})
@ -286,10 +285,10 @@ func TestContainerListFormatSizeSetsOption(t *testing.T) {
func TestContainerListWithConfigFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
return []types.Container{
*builders.Container("c1", builders.WithLabel("some.label", "value"), builders.WithSize(10700000)),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar"), builders.WithSize(3200000)),
*Container("c1", WithLabel("some.label", "value"), WithSize(10700000)),
*Container("c2", WithName("foo/bar"), WithLabel("foo", "bar"), WithSize(3200000)),
}, nil
},
})
@ -303,10 +302,10 @@ func TestContainerListWithConfigFormat(t *testing.T) {
func TestContainerListWithFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) {
return []types.Container{
*builders.Container("c1", builders.WithLabel("some.label", "value")),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")),
*Container("c1", WithLabel("some.label", "value")),
*Container("c2", WithName("foo/bar"), WithLabel("foo", "bar")),
}, nil
},
})

View File

@ -7,7 +7,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stdcopy"
"github.com/spf13/cobra"
)
@ -33,7 +33,7 @@ func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.container = args[0]
return runLogs(cmd.Context(), dockerCli, &opts)
return runLogs(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container logs, docker logs",
@ -52,13 +52,15 @@ func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runLogs(ctx context.Context, dockerCli command.Cli, opts *logsOptions) error {
func runLogs(dockerCli command.Cli, opts *logsOptions) error {
ctx := context.Background()
c, err := dockerCli.Client().ContainerInspect(ctx, opts.container)
if err != nil {
return err
}
responseBody, err := dockerCli.Client().ContainerLogs(ctx, c.ID, container.LogsOptions{
options := types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Since: opts.since,
@ -67,7 +69,8 @@ func runLogs(ctx context.Context, dockerCli command.Cli, opts *logsOptions) erro
Follow: opts.follow,
Tail: opts.tail,
Details: opts.details,
})
}
responseBody, err := dockerCli.Client().ContainerLogs(ctx, c.ID, options)
if err != nil {
return err
}

View File

@ -1,7 +1,6 @@
package container
import (
"context"
"io"
"strings"
"testing"
@ -13,8 +12,8 @@ import (
is "gotest.tools/v3/assert/cmp"
)
var logFn = func(expectedOut string) func(string, container.LogsOptions) (io.ReadCloser, error) {
return func(container string, opts container.LogsOptions) (io.ReadCloser, error) {
var logFn = func(expectedOut string) func(string, types.ContainerLogsOptions) (io.ReadCloser, error) {
return func(container string, opts types.ContainerLogsOptions) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader(expectedOut)), nil
}
}
@ -47,11 +46,13 @@ func TestRunLogs(t *testing.T) {
t.Run(testcase.doc, func(t *testing.T) {
cli := test.NewFakeCli(&testcase.client)
err := runLogs(context.TODO(), cli, testcase.options)
err := runLogs(cli, testcase.options)
if testcase.expectedError != "" {
assert.ErrorContains(t, err, testcase.expectedError)
} else if !assert.Check(t, err) {
return
} else {
if !assert.Check(t, err) {
return
}
}
assert.Check(t, is.Equal(testcase.expectedOut, cli.OutBuffer().String()))
assert.Check(t, is.Equal(testcase.expectedErr, cli.ErrBuffer().String()))

View File

@ -13,133 +13,117 @@ import (
"strings"
"time"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
mounttypes "github.com/docker/docker/api/types/mount"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/errdefs"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
cdi "tags.cncf.io/container-device-interface/pkg/parser"
)
const (
// TODO(thaJeztah): define these in the API-types, or query available defaults
// from the daemon, or require "local" profiles to be an absolute path or
// relative paths starting with "./". The daemon-config has consts for this
// but we don't want to import that package:
// https://github.com/moby/moby/blob/v23.0.0/daemon/config/config.go#L63-L67
// seccompProfileDefault is the built-in default seccomp profile.
seccompProfileDefault = "builtin"
// seccompProfileUnconfined is a special profile name for seccomp to use an
// "unconfined" seccomp profile.
seccompProfileUnconfined = "unconfined"
)
var deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`)
// containerOptions is a data object with all the options for creating a container
type containerOptions struct {
attach opts.ListOpts
volumes opts.ListOpts
tmpfs opts.ListOpts
mounts opts.MountOpt
blkioWeightDevice opts.WeightdeviceOpt
deviceReadBps opts.ThrottledeviceOpt
deviceWriteBps opts.ThrottledeviceOpt
links opts.ListOpts
aliases opts.ListOpts
linkLocalIPs opts.ListOpts
deviceReadIOps opts.ThrottledeviceOpt
deviceWriteIOps opts.ThrottledeviceOpt
env opts.ListOpts
labels opts.ListOpts
deviceCgroupRules opts.ListOpts
devices opts.ListOpts
gpus opts.GpuOpts
ulimits *opts.UlimitOpt
sysctls *opts.MapOpts
publish opts.ListOpts
expose opts.ListOpts
dns opts.ListOpts
dnsSearch opts.ListOpts
dnsOptions opts.ListOpts
extraHosts opts.ListOpts
volumesFrom opts.ListOpts
envFile opts.ListOpts
capAdd opts.ListOpts
capDrop opts.ListOpts
groupAdd opts.ListOpts
securityOpt opts.ListOpts
storageOpt opts.ListOpts
labelsFile opts.ListOpts
loggingOpts opts.ListOpts
privileged bool
pidMode string
utsMode string
usernsMode string
cgroupnsMode string
publishAll bool
stdin bool
tty bool
oomKillDisable bool
oomScoreAdj int
containerIDFile string
entrypoint string
hostname string
domainname string
memory opts.MemBytes
memoryReservation opts.MemBytes
memorySwap opts.MemSwapBytes
kernelMemory opts.MemBytes
user string
workingDir string
cpuCount int64
cpuShares int64
cpuPercent int64
cpuPeriod int64
cpuRealtimePeriod int64
cpuRealtimeRuntime int64
cpuQuota int64
cpus opts.NanoCPUs
cpusetCpus string
cpusetMems string
blkioWeight uint16
ioMaxBandwidth opts.MemBytes
ioMaxIOps uint64
swappiness int64
netMode opts.NetworkOpt
macAddress string
ipv4Address string
ipv6Address string
ipcMode string
pidsLimit int64
restartPolicy string
readonlyRootfs bool
loggingDriver string
cgroupParent string
volumeDriver string
stopSignal string
stopTimeout int
isolation string
shmSize opts.MemBytes
noHealthcheck bool
healthCmd string
healthInterval time.Duration
healthTimeout time.Duration
healthStartPeriod time.Duration
healthStartInterval time.Duration
healthRetries int
runtime string
autoRemove bool
init bool
annotations *opts.MapOpts
attach opts.ListOpts
volumes opts.ListOpts
tmpfs opts.ListOpts
mounts opts.MountOpt
blkioWeightDevice opts.WeightdeviceOpt
deviceReadBps opts.ThrottledeviceOpt
deviceWriteBps opts.ThrottledeviceOpt
links opts.ListOpts
aliases opts.ListOpts
linkLocalIPs opts.ListOpts
deviceReadIOps opts.ThrottledeviceOpt
deviceWriteIOps opts.ThrottledeviceOpt
env opts.ListOpts
labels opts.ListOpts
deviceCgroupRules opts.ListOpts
devices opts.ListOpts
gpus opts.GpuOpts
ulimits *opts.UlimitOpt
sysctls *opts.MapOpts
publish opts.ListOpts
expose opts.ListOpts
dns opts.ListOpts
dnsSearch opts.ListOpts
dnsOptions opts.ListOpts
extraHosts opts.ListOpts
volumesFrom opts.ListOpts
envFile opts.ListOpts
capAdd opts.ListOpts
capDrop opts.ListOpts
groupAdd opts.ListOpts
securityOpt opts.ListOpts
storageOpt opts.ListOpts
labelsFile opts.ListOpts
loggingOpts opts.ListOpts
privileged bool
pidMode string
utsMode string
usernsMode string
cgroupnsMode string
publishAll bool
stdin bool
tty bool
oomKillDisable bool
oomScoreAdj int
containerIDFile string
entrypoint string
hostname string
domainname string
memory opts.MemBytes
memoryReservation opts.MemBytes
memorySwap opts.MemSwapBytes
kernelMemory opts.MemBytes
user string
workingDir string
cpuCount int64
cpuShares int64
cpuPercent int64
cpuPeriod int64
cpuRealtimePeriod int64
cpuRealtimeRuntime int64
cpuQuota int64
cpus opts.NanoCPUs
cpusetCpus string
cpusetMems string
blkioWeight uint16
ioMaxBandwidth opts.MemBytes
ioMaxIOps uint64
swappiness int64
netMode opts.NetworkOpt
macAddress string
ipv4Address string
ipv6Address string
ipcMode string
pidsLimit int64
restartPolicy string
readonlyRootfs bool
loggingDriver string
cgroupParent string
volumeDriver string
stopSignal string
stopTimeout int
isolation string
shmSize opts.MemBytes
noHealthcheck bool
healthCmd string
healthInterval time.Duration
healthTimeout time.Duration
healthStartPeriod time.Duration
healthRetries int
runtime string
autoRemove bool
init bool
annotations *opts.MapOpts
Image string
Args []string
@ -199,7 +183,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
flags.VarP(&copts.labels, "label", "l", "Set meta data on a container")
flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels")
flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only")
flags.StringVar(&copts.restartPolicy, "restart", string(container.RestartPolicyDisabled), "Restart policy to apply when a container exits")
flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits")
flags.StringVar(&copts.stopSignal, "stop-signal", "", "Signal to stop the container")
flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container")
flags.SetAnnotation("stop-timeout", "version", []string{"1.25"})
@ -266,8 +250,6 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ms|s|m|h) (default 0s)")
flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s)")
flags.SetAnnotation("health-start-period", "version", []string{"1.29"})
flags.DurationVar(&copts.healthStartInterval, "health-start-interval", 0, "Time between running the check during the start period (ms|s|m|h) (default 0s)")
flags.SetAnnotation("health-start-interval", "version", []string{"1.44"})
flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
// Resource management
@ -372,10 +354,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
volumes := copts.volumes.GetMap()
// add any bind targets to the list of container volumes
for bind := range copts.volumes.GetMap() {
parsed, err := loader.ParseVolume(bind)
if err != nil {
return nil, err
}
parsed, _ := loader.ParseVolume(bind)
if parsed.Source != "" {
toBind := bind
@ -470,17 +449,12 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
// parsing flags, we haven't yet sent a _ping to the daemon to determine
// what operating system it is.
deviceMappings := []container.DeviceMapping{}
var cdiDeviceNames []string
for _, device := range copts.devices.GetAll() {
var (
validated string
deviceMapping container.DeviceMapping
err error
)
if cdi.IsQualifiedName(device) {
cdiDeviceNames = append(cdiDeviceNames, device)
continue
}
validated, err = validateDevice(device, serverOS)
if err != nil {
return nil, err
@ -552,8 +526,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
copts.healthInterval != 0 ||
copts.healthTimeout != 0 ||
copts.healthStartPeriod != 0 ||
copts.healthRetries != 0 ||
copts.healthStartInterval != 0
copts.healthRetries != 0
if copts.noHealthcheck {
if haveHealthSettings {
return nil, errors.Errorf("--no-healthcheck conflicts with --health-* options")
@ -576,29 +549,16 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
if copts.healthStartPeriod < 0 {
return nil, fmt.Errorf("--health-start-period cannot be negative")
}
if copts.healthStartInterval < 0 {
return nil, fmt.Errorf("--health-start-interval cannot be negative")
}
healthConfig = &container.HealthConfig{
Test: probe,
Interval: copts.healthInterval,
Timeout: copts.healthTimeout,
StartPeriod: copts.healthStartPeriod,
StartInterval: copts.healthStartInterval,
Retries: copts.healthRetries,
Test: probe,
Interval: copts.healthInterval,
Timeout: copts.healthTimeout,
StartPeriod: copts.healthStartPeriod,
Retries: copts.healthRetries,
}
}
deviceRequests := copts.gpus.Value()
if len(cdiDeviceNames) > 0 {
cdiDeviceRequest := container.DeviceRequest{
Driver: "cdi",
DeviceIDs: cdiDeviceNames,
}
deviceRequests = append(deviceRequests, cdiDeviceRequest)
}
resources := container.Resources{
CgroupParent: copts.cgroupParent,
Memory: copts.memory.Value(),
@ -629,7 +589,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
Ulimits: copts.ulimits.GetList(),
DeviceCgroupRules: copts.deviceCgroupRules.GetAll(),
Devices: deviceMappings,
DeviceRequests: deviceRequests,
DeviceRequests: copts.gpus.Value(),
}
config := &container.Config{
@ -726,12 +686,6 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
return nil, err
}
// Put the endpoint-specific MacAddress of the "main" network attachment into the container Config for backward
// compatibility with older daemons.
if nw, ok := networkingConfig.EndpointsConfig[hostConfig.NetworkMode.NetworkName()]; ok {
config.MacAddress = nw.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
}
return &containerConfig{
Config: config,
HostConfig: hostConfig,
@ -752,20 +706,6 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin
hasUserDefined, hasNonUserDefined bool
)
if len(copts.netMode.Value()) == 0 {
n := opts.NetworkAttachmentOpts{
Target: "default",
}
if err := applyContainerOptions(&n, copts); err != nil {
return nil, err
}
ep, err := parseNetworkAttachmentOpt(n)
if err != nil {
return nil, err
}
endpoints["default"] = ep
}
for i, n := range copts.netMode.Value() {
n := n
if container.NetworkMode(n.Target).IsUserDefined() {
@ -807,7 +747,8 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin
return endpoints, nil
}
func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOptions) error { //nolint:gocyclo
func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOptions) error {
// TODO should copts.MacAddress actually be set on the first network? (currently it's not)
// TODO should we error if _any_ advanced option is used? (i.e. forbid to combine advanced notation with the "old" flags (`--network-alias`, `--link`, `--ip`, `--ip6`)?
if len(n.Aliases) > 0 && copts.aliases.Len() > 0 {
return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --network-alias and per-network alias"))
@ -821,17 +762,11 @@ func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOption
if n.IPv6Address != "" && copts.ipv6Address != "" {
return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address"))
}
if n.MacAddress != "" && copts.macAddress != "" {
return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --mac-address and per-network MAC address"))
}
if len(n.LinkLocalIPs) > 0 && copts.linkLocalIPs.Len() > 0 {
return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --link-local-ip and per-network link-local IP addresses"))
}
if copts.aliases.Len() > 0 {
n.Aliases = make([]string, copts.aliases.Len())
copy(n.Aliases, copts.aliases.GetAll())
}
if n.Target != "default" && copts.links.Len() > 0 {
if copts.links.Len() > 0 {
n.Links = make([]string, copts.links.Len())
copy(n.Links, copts.links.GetAll())
}
@ -841,9 +776,8 @@ func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOption
if copts.ipv6Address != "" {
n.IPv6Address = copts.ipv6Address
}
if copts.macAddress != "" {
n.MacAddress = copts.macAddress
}
// TODO should linkLocalIPs be added to the _first_ network only, or to _all_ networks? (should this be a per-network option as well?)
if copts.linkLocalIPs.Len() > 0 {
n.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len())
copy(n.LinkLocalIPs, copts.linkLocalIPs.GetAll())
@ -880,12 +814,6 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End
LinkLocalIPs: ep.LinkLocalIPs,
}
}
if ep.MacAddress != "" {
if _, err := opts.ValidateMACAddress(ep.MacAddress); err != nil {
return nil, errors.Errorf("%s is not a valid mac address", ep.MacAddress)
}
epConfig.MacAddress = ep.MacAddress
}
return epConfig, nil
}
@ -928,23 +856,16 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) {
// "no-new-privileges" is the only option that does not require a value.
return securityOpts, errors.Errorf("Invalid --security-opt: %q", opt)
}
if k == "seccomp" {
switch v {
case seccompProfileDefault, seccompProfileUnconfined:
// known special names for built-in profiles, nothing to do.
default:
// value may be a filename, in which case we send the profile's
// content if it's valid JSON.
f, err := os.ReadFile(v)
if err != nil {
return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", v, err)
}
b := bytes.NewBuffer(nil)
if err := json.Compact(b, f); err != nil {
return securityOpts, errors.Errorf("compacting json for seccomp profile (%s) failed: %v", v, err)
}
securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
if k == "seccomp" && v != "unconfined" {
f, err := os.ReadFile(v)
if err != nil {
return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", v, err)
}
b := bytes.NewBuffer(nil)
if err := json.Compact(b, f); err != nil {
return securityOpts, errors.Errorf("compacting json for seccomp profile (%s) failed: %v", v, err)
}
securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
}
}
@ -1140,8 +1061,8 @@ func validateAttach(val string) (string, error) {
func validateAPIVersion(c *containerConfig, serverAPIVersion string) error {
for _, m := range c.HostConfig.Mounts {
if err := command.ValidateMountWithAPIVersion(m, serverAPIVersion); err != nil {
return err
if m.BindOptions != nil && m.BindOptions.NonRecursive && versions.LessThan(serverAPIVersion, "1.40") {
return errors.Errorf("bind-nonrecursive requires API v1.40 or later")
}
}
return nil

View File

@ -64,21 +64,21 @@ func setupRunFlags() (*pflag.FlagSet, *containerOptions) {
return flags, copts
}
func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig) {
func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
t.Helper()
config, hostConfig, nwConfig, err := parseRun(append(strings.Split(args, " "), "ubuntu", "bash"))
config, hostConfig, _, err := parseRun(append(strings.Split(args, " "), "ubuntu", "bash"))
assert.NilError(t, err)
return config, hostConfig, nwConfig
return config, hostConfig
}
func TestParseRunLinks(t *testing.T) {
if _, hostConfig, _ := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
}
if _, hostConfig, _ := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
}
if _, hostConfig, _ := mustParse(t, ""); len(hostConfig.Links) != 0 {
if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
}
}
@ -128,7 +128,7 @@ func TestParseRunAttach(t *testing.T) {
for _, tc := range tests {
tc := tc
t.Run(tc.input, func(t *testing.T) {
config, _, _ := mustParse(t, tc.input)
config, _ := mustParse(t, tc.input)
assert.Equal(t, config.AttachStdin, tc.expected.AttachStdin)
assert.Equal(t, config.AttachStdout, tc.expected.AttachStdout)
assert.Equal(t, config.AttachStderr, tc.expected.AttachStderr)
@ -186,7 +186,7 @@ func TestParseRunWithInvalidArgs(t *testing.T) {
func TestParseWithVolumes(t *testing.T) {
// A single volume
arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds != nil {
if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
} else if _, exists := config.Volumes[arr[0]]; !exists {
t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes)
@ -194,23 +194,23 @@ func TestParseWithVolumes(t *testing.T) {
// Two volumes
arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds != nil {
if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
} else if _, exists := config.Volumes[arr[0]]; !exists {
t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes)
} else if _, exists := config.Volumes[arr[1]]; !exists { //nolint:govet // ignore shadow-check
} else if _, exists := config.Volumes[arr[1]]; !exists {
t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes)
}
// A single bind mount
arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`})
if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
t.Fatalf("Error parsing volume flags, %q should mount-bind the path before the colon into the path after the colon. Received %v %v", arr[0], hostConfig.Binds, config.Volumes)
}
// Two bind mounts.
arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`})
if _, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
}
@ -219,26 +219,26 @@ func TestParseWithVolumes(t *testing.T) {
arr, tryit = setupPlatformVolume(
[]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`},
[]string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`})
if _, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
}
// Similar to previous test but with alternate modes which are only supported by Linux
if runtime.GOOS != "windows" {
arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{})
if _, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
}
arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{})
if _, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
}
}
// One bind mount and one volume
arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`})
if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
t.Fatalf("Error parsing volume flags, %s and %s should only one and only one bind mount %s. Received %s", arr[0], arr[1], arr[0], hostConfig.Binds)
} else if _, exists := config.Volumes[arr[1]]; !exists {
t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes)
@ -247,7 +247,7 @@ func TestParseWithVolumes(t *testing.T) {
// Root to non-c: drive letter (Windows specific)
if runtime.GOOS == "windows" {
arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`})
if config, hostConfig, _ := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
}
}
@ -290,14 +290,8 @@ func TestParseWithMacAddress(t *testing.T) {
if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
}
config, hostConfig, nwConfig := mustParse(t, validMacAddress)
if config.MacAddress != "92:d0:c6:0a:29:33" { //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as container-wide MacAddress, got '%v'",
config.MacAddress) //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
}
defaultNw := hostConfig.NetworkMode.NetworkName()
if nwConfig.EndpointsConfig[defaultNw].MacAddress != "92:d0:c6:0a:29:33" {
t.Fatalf("Expected the default endpoint to have the MacAddress '92:d0:c6:0a:29:33' set, got '%v'", nwConfig.EndpointsConfig[defaultNw].MacAddress)
if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress)
}
}
@ -307,7 +301,7 @@ func TestRunFlagsParseWithMemory(t *testing.T) {
err := flags.Parse(args)
assert.ErrorContains(t, err, `invalid argument "invalid" for "-m, --memory" flag`)
_, hostconfig, _ := mustParse(t, "--memory=1G")
_, hostconfig := mustParse(t, "--memory=1G")
assert.Check(t, is.Equal(int64(1073741824), hostconfig.Memory))
}
@ -317,10 +311,10 @@ func TestParseWithMemorySwap(t *testing.T) {
err := flags.Parse(args)
assert.ErrorContains(t, err, `invalid argument "invalid" for "--memory-swap" flag`)
_, hostconfig, _ := mustParse(t, "--memory-swap=1G")
_, hostconfig := mustParse(t, "--memory-swap=1G")
assert.Check(t, is.Equal(int64(1073741824), hostconfig.MemorySwap))
_, hostconfig, _ = mustParse(t, "--memory-swap=-1")
_, hostconfig = mustParse(t, "--memory-swap=-1")
assert.Check(t, is.Equal(int64(-1), hostconfig.MemorySwap))
}
@ -335,14 +329,14 @@ func TestParseHostname(t *testing.T) {
hostnameWithDomain := "--hostname=hostname.domainname"
hostnameWithDomainTld := "--hostname=hostname.domainname.tld"
for hostname, expectedHostname := range validHostnames {
if config, _, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname {
if config, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname {
t.Fatalf("Expected the config to have 'hostname' as %q, got %q", expectedHostname, config.Hostname)
}
}
if config, _, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname.domainname" || config.Domainname != "" {
if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname.domainname" || config.Domainname != "" {
t.Fatalf("Expected the config to have 'hostname' as hostname.domainname, got %q", config.Hostname)
}
if config, _, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname.domainname.tld" || config.Domainname != "" {
if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname.domainname.tld" || config.Domainname != "" {
t.Fatalf("Expected the config to have 'hostname' as hostname.domainname.tld, got %q", config.Hostname)
}
}
@ -356,14 +350,14 @@ func TestParseHostnameDomainname(t *testing.T) {
"domainname-63-bytes-long-should-be-valid-and-without-any-errors": "domainname-63-bytes-long-should-be-valid-and-without-any-errors",
}
for domainname, expectedDomainname := range validDomainnames {
if config, _, _ := mustParse(t, "--domainname="+domainname); config.Domainname != expectedDomainname {
if config, _ := mustParse(t, "--domainname="+domainname); config.Domainname != expectedDomainname {
t.Fatalf("Expected the config to have 'domainname' as %q, got %q", expectedDomainname, config.Domainname)
}
}
if config, _, _ := mustParse(t, "--hostname=some.prefix --domainname=domainname"); config.Hostname != "some.prefix" || config.Domainname != "domainname" {
if config, _ := mustParse(t, "--hostname=some.prefix --domainname=domainname"); config.Hostname != "some.prefix" || config.Domainname != "domainname" {
t.Fatalf("Expected the config to have 'hostname' as 'some.prefix' and 'domainname' as 'domainname', got %q and %q", config.Hostname, config.Domainname)
}
if config, _, _ := mustParse(t, "--hostname=another-prefix --domainname=domainname.tld"); config.Hostname != "another-prefix" || config.Domainname != "domainname.tld" {
if config, _ := mustParse(t, "--hostname=another-prefix --domainname=domainname.tld"); config.Hostname != "another-prefix" || config.Domainname != "domainname.tld" {
t.Fatalf("Expected the config to have 'hostname' as 'another-prefix' and 'domainname' as 'domainname.tld', got %q and %q", config.Hostname, config.Domainname)
}
}
@ -372,8 +366,8 @@ func TestParseWithExpose(t *testing.T) {
invalids := map[string]string{
":": "invalid port format for --expose: :",
"8080:9090": "invalid port format for --expose: 8080:9090",
"/tcp": "invalid range format for --expose: /tcp, error: empty string specified for ports",
"/udp": "invalid range format for --expose: /udp, error: empty string specified for ports",
"/tcp": "invalid range format for --expose: /tcp, error: Empty string specified for ports.",
"/udp": "invalid range format for --expose: /udp, error: Empty string specified for ports.",
"NaN/tcp": `invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
"NaN-NaN/tcp": `invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
"8080-NaN/tcp": `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
@ -423,114 +417,61 @@ func TestParseWithExpose(t *testing.T) {
func TestParseDevice(t *testing.T) {
skip.If(t, runtime.GOOS != "linux") // Windows and macOS validate server-side
testCases := []struct {
devices []string
deviceMapping *container.DeviceMapping
deviceRequests []container.DeviceRequest
}{
{
devices: []string{"/dev/snd"},
deviceMapping: &container.DeviceMapping{
PathOnHost: "/dev/snd",
PathInContainer: "/dev/snd",
CgroupPermissions: "rwm",
},
valids := map[string]container.DeviceMapping{
"/dev/snd": {
PathOnHost: "/dev/snd",
PathInContainer: "/dev/snd",
CgroupPermissions: "rwm",
},
{
devices: []string{"/dev/snd:rw"},
deviceMapping: &container.DeviceMapping{
PathOnHost: "/dev/snd",
PathInContainer: "/dev/snd",
CgroupPermissions: "rw",
},
"/dev/snd:rw": {
PathOnHost: "/dev/snd",
PathInContainer: "/dev/snd",
CgroupPermissions: "rw",
},
{
devices: []string{"/dev/snd:/something"},
deviceMapping: &container.DeviceMapping{
PathOnHost: "/dev/snd",
PathInContainer: "/something",
CgroupPermissions: "rwm",
},
"/dev/snd:/something": {
PathOnHost: "/dev/snd",
PathInContainer: "/something",
CgroupPermissions: "rwm",
},
{
devices: []string{"/dev/snd:/something:rw"},
deviceMapping: &container.DeviceMapping{
PathOnHost: "/dev/snd",
PathInContainer: "/something",
CgroupPermissions: "rw",
},
},
{
devices: []string{"vendor.com/class=name"},
deviceMapping: nil,
deviceRequests: []container.DeviceRequest{
{
Driver: "cdi",
DeviceIDs: []string{"vendor.com/class=name"},
},
},
},
{
devices: []string{"vendor.com/class=name", "/dev/snd:/something:rw"},
deviceMapping: &container.DeviceMapping{
PathOnHost: "/dev/snd",
PathInContainer: "/something",
CgroupPermissions: "rw",
},
deviceRequests: []container.DeviceRequest{
{
Driver: "cdi",
DeviceIDs: []string{"vendor.com/class=name"},
},
},
"/dev/snd:/something:rw": {
PathOnHost: "/dev/snd",
PathInContainer: "/something",
CgroupPermissions: "rw",
},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("%s", tc.devices), func(t *testing.T) {
var args []string
for _, d := range tc.devices {
args = append(args, fmt.Sprintf("--device=%v", d))
}
args = append(args, "img", "cmd")
_, hostconfig, _, err := parseRun(args)
assert.NilError(t, err)
if tc.deviceMapping != nil {
if assert.Check(t, is.Len(hostconfig.Devices, 1)) {
assert.Check(t, is.DeepEqual(*tc.deviceMapping, hostconfig.Devices[0]))
}
} else {
assert.Check(t, is.Len(hostconfig.Devices, 0))
}
assert.Check(t, is.DeepEqual(tc.deviceRequests, hostconfig.DeviceRequests))
})
for device, deviceMapping := range valids {
_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
if err != nil {
t.Fatal(err)
}
if len(hostconfig.Devices) != 1 {
t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices)
}
if hostconfig.Devices[0] != deviceMapping {
t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
}
}
}
func TestParseNetworkConfig(t *testing.T) {
tests := []struct {
name string
flags []string
expected map[string]*networktypes.EndpointSettings
expectedCfg container.Config
expectedHostCfg container.HostConfig
expectedErr string
name string
flags []string
expected map[string]*networktypes.EndpointSettings
expectedCfg container.HostConfig
expectedErr string
}{
{
name: "single-network-legacy",
flags: []string{"--network", "net1"},
expected: map[string]*networktypes.EndpointSettings{},
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
name: "single-network-legacy",
flags: []string{"--network", "net1"},
expected: map[string]*networktypes.EndpointSettings{},
expectedCfg: container.HostConfig{NetworkMode: "net1"},
},
{
name: "single-network-advanced",
flags: []string{"--network", "name=net1"},
expected: map[string]*networktypes.EndpointSettings{},
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
name: "single-network-advanced",
flags: []string{"--network", "name=net1"},
expected: map[string]*networktypes.EndpointSettings{},
expectedCfg: container.HostConfig{NetworkMode: "net1"},
},
{
name: "single-network-legacy-with-options",
@ -556,7 +497,7 @@ func TestParseNetworkConfig(t *testing.T) {
Aliases: []string{"web1", "web2"},
},
},
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
expectedCfg: container.HostConfig{NetworkMode: "net1"},
},
{
name: "multiple-network-advanced-mixed",
@ -572,7 +513,6 @@ func TestParseNetworkConfig(t *testing.T) {
"--network-alias", "web2",
"--network", "net2",
"--network", "name=net3,alias=web3,driver-opt=field3=value3,ip=172.20.88.22,ip6=2001:db8::8822",
"--network", "name=net4,mac-address=02:32:1c:23:00:04,link-local-ip=169.254.169.254",
},
expected: map[string]*networktypes.EndpointSettings{
"net1": {
@ -594,18 +534,12 @@ func TestParseNetworkConfig(t *testing.T) {
},
Aliases: []string{"web3"},
},
"net4": {
MacAddress: "02:32:1c:23:00:04",
IPAMConfig: &networktypes.EndpointIPAMConfig{
LinkLocalIPs: []string{"169.254.169.254"},
},
},
},
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
expectedCfg: container.HostConfig{NetworkMode: "net1"},
},
{
name: "single-network-advanced-with-options",
flags: []string{"--network", "name=net1,alias=web1,alias=web2,driver-opt=field1=value1,driver-opt=field2=value2,ip=172.20.88.22,ip6=2001:db8::8822,mac-address=02:32:1c:23:00:04"},
flags: []string{"--network", "name=net1,alias=web1,alias=web2,driver-opt=field1=value1,driver-opt=field2=value2,ip=172.20.88.22,ip6=2001:db8::8822"},
expected: map[string]*networktypes.EndpointSettings{
"net1": {
DriverOpts: map[string]string{
@ -616,30 +550,16 @@ func TestParseNetworkConfig(t *testing.T) {
IPv4Address: "172.20.88.22",
IPv6Address: "2001:db8::8822",
},
Aliases: []string{"web1", "web2"},
MacAddress: "02:32:1c:23:00:04",
Aliases: []string{"web1", "web2"},
},
},
expectedCfg: container.Config{MacAddress: "02:32:1c:23:00:04"},
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
expectedCfg: container.HostConfig{NetworkMode: "net1"},
},
{
name: "multiple-networks",
flags: []string{"--network", "net1", "--network", "name=net2"},
expected: map[string]*networktypes.EndpointSettings{"net1": {}, "net2": {}},
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
},
{
name: "advanced-options-with-standalone-mac-address-flag",
flags: []string{"--network=name=net1,alias=foobar", "--mac-address", "52:0f:f3:dc:50:10"},
expected: map[string]*networktypes.EndpointSettings{
"net1": {
Aliases: []string{"foobar"},
MacAddress: "52:0f:f3:dc:50:10",
},
},
expectedCfg: container.Config{MacAddress: "52:0f:f3:dc:50:10"},
expectedHostCfg: container.HostConfig{NetworkMode: "net1"},
name: "multiple-networks",
flags: []string{"--network", "net1", "--network", "name=net2"},
expected: map[string]*networktypes.EndpointSettings{"net1": {}, "net2": {}},
expectedCfg: container.HostConfig{NetworkMode: "net1"},
},
{
name: "conflict-network",
@ -666,26 +586,11 @@ func TestParseNetworkConfig(t *testing.T) {
flags: []string{"--network", "name=host", "--network", "net1"},
expectedErr: `conflicting options: cannot attach both user-defined and non-user-defined network-modes`,
},
{
name: "conflict-options-link-local-ip",
flags: []string{"--network", "name=net1,link-local-ip=169.254.169.254", "--link-local-ip", "169.254.10.8"},
expectedErr: `conflicting options: cannot specify both --link-local-ip and per-network link-local IP addresses`,
},
{
name: "conflict-options-mac-address",
flags: []string{"--network", "name=net1,mac-address=02:32:1c:23:00:04", "--mac-address", "02:32:1c:23:00:04"},
expectedErr: `conflicting options: cannot specify both --mac-address and per-network MAC address`,
},
{
name: "invalid-mac-address",
flags: []string{"--network", "name=net1,mac-address=foobar"},
expectedErr: "foobar is not a valid mac address",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
config, hConfig, nwConfig, err := parseRun(tc.flags)
_, hConfig, nwConfig, err := parseRun(tc.flags)
if tc.expectedErr != "" {
assert.Error(t, err, tc.expectedErr)
@ -693,8 +598,7 @@ func TestParseNetworkConfig(t *testing.T) {
}
assert.NilError(t, err)
assert.DeepEqual(t, config.MacAddress, tc.expectedCfg.MacAddress) //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
assert.DeepEqual(t, hConfig.NetworkMode, tc.expectedHostCfg.NetworkMode)
assert.DeepEqual(t, hConfig.NetworkMode, tc.expectedCfg.NetworkMode)
assert.DeepEqual(t, nwConfig.EndpointsConfig, tc.expected)
})
}
@ -744,75 +648,34 @@ func TestRunFlagsParseShmSize(t *testing.T) {
}
func TestParseRestartPolicy(t *testing.T) {
tests := []struct {
input string
expected container.RestartPolicy
expectedErr string
}{
{
input: "",
invalids := map[string]string{
"always:2:3": "invalid restart policy format: maximum retry count must be an integer",
"on-failure:invalid": "invalid restart policy format: maximum retry count must be an integer",
}
valids := map[string]container.RestartPolicy{
"": {},
"always": {
Name: "always",
MaximumRetryCount: 0,
},
{
input: "no",
expected: container.RestartPolicy{
Name: container.RestartPolicyDisabled,
},
},
{
input: ":1",
expectedErr: "invalid restart policy format: no policy provided before colon",
},
{
input: "always",
expected: container.RestartPolicy{
Name: container.RestartPolicyAlways,
},
},
{
input: "always:1",
expected: container.RestartPolicy{
Name: container.RestartPolicyAlways,
MaximumRetryCount: 1,
},
},
{
input: "always:2:3",
expectedErr: "invalid restart policy format: maximum retry count must be an integer",
},
{
input: "on-failure:1",
expected: container.RestartPolicy{
Name: container.RestartPolicyOnFailure,
MaximumRetryCount: 1,
},
},
{
input: "on-failure:invalid",
expectedErr: "invalid restart policy format: maximum retry count must be an integer",
},
{
input: "unless-stopped",
expected: container.RestartPolicy{
Name: container.RestartPolicyUnlessStopped,
},
},
{
input: "unless-stopped:invalid",
expectedErr: "invalid restart policy format: maximum retry count must be an integer",
"on-failure:1": {
Name: "on-failure",
MaximumRetryCount: 1,
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.input, func(t *testing.T) {
_, hostConfig, _, err := parseRun([]string{"--restart=" + tc.input, "img", "cmd"})
if tc.expectedErr != "" {
assert.Check(t, is.Error(err, tc.expectedErr))
assert.Check(t, is.Nil(hostConfig))
} else {
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(hostConfig.RestartPolicy, tc.expected))
}
})
for restart, expectedError := range invalids {
if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
}
}
for restart, expected := range valids {
_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
if err != nil {
t.Fatal(err)
}
if hostconfig.RestartPolicy != expected {
t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy)
}
}
}
@ -857,8 +720,8 @@ func TestParseHealth(t *testing.T) {
checkError("--no-healthcheck conflicts with --health-* options",
"--no-healthcheck", "--health-cmd=/check.sh -q", "img", "cmd")
health = checkOk("--health-timeout=2s", "--health-retries=3", "--health-interval=4.5s", "--health-start-period=5s", "--health-start-interval=1s", "img", "cmd")
if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond || health.StartPeriod != 5*time.Second || health.StartInterval != 1*time.Second {
health = checkOk("--health-timeout=2s", "--health-retries=3", "--health-interval=4.5s", "--health-start-period=5s", "img", "cmd")
if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond || health.StartPeriod != 5*time.Second {
t.Fatalf("--health-*: got %#v", health)
}
}
@ -1011,8 +874,10 @@ func TestValidateDevice(t *testing.T) {
for path, expectedError := range invalid {
if _, err := validateDevice(path, runtime.GOOS); err == nil {
t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
} else if err.Error() != expectedError {
t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
} else {
if err.Error() != expectedError {
t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
}
}
}
}

View File

@ -27,7 +27,7 @@ func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args
return runPause(cmd.Context(), dockerCli, &opts)
return runPause(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container pause, docker pause",
@ -38,7 +38,9 @@ func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
}
}
func runPause(ctx context.Context, dockerCli command.Cli, opts *pauseOptions) error {
func runPause(dockerCli command.Cli, opts *pauseOptions) error {
ctx := context.Background()
var errs []string
errChan := parallelOperation(ctx, opts.containers, dockerCli.Client().ContainerPause)
for _, container := range opts.containers {

View File

@ -36,7 +36,7 @@ func NewPortCommand(dockerCli command.Cli) *cobra.Command {
if len(args) > 1 {
opts.port = args[1]
}
return runPort(cmd.Context(), dockerCli, &opts)
return runPort(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container port, docker port",
@ -52,7 +52,9 @@ func NewPortCommand(dockerCli command.Cli) *cobra.Command {
// TODO(thaJeztah): currently this defaults to show the TCP port if no
// proto is specified. We should consider changing this to "any" protocol
// for the given private port.
func runPort(ctx context.Context, dockerCli command.Cli, opts *portOptions) error {
func runPort(dockerCli command.Cli, opts *portOptions) error {
ctx := context.Background()
c, err := dockerCli.Client().ContainerInspect(ctx, opts.container)
if err != nil {
return err

View File

@ -26,7 +26,7 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
Short: "Remove all stopped containers",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCli, options)
spaceReclaimed, output, err := runPrune(dockerCli, options)
if err != nil {
return err
}
@ -50,14 +50,14 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
const warning = `WARNING! This will remove all stopped containers.
Are you sure you want to continue?`
func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
return 0, "", nil
}
report, err := dockerCli.Client().ContainersPrune(ctx, pruneFilters)
report, err := dockerCli.Client().ContainersPrune(context.Background(), pruneFilters)
if err != nil {
return 0, "", err
}
@ -75,6 +75,6 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
// RunPrune calls the Container Prune API
// This returns the amount of space reclaimed and a detailed output string
func RunPrune(ctx context.Context, dockerCli command.Cli, _ bool, filter opts.FilterOpt) (uint64, string, error) {
return runPrune(ctx, dockerCli, pruneOptions{force: true, filter: filter})
func RunPrune(dockerCli command.Cli, _ bool, filter opts.FilterOpt) (uint64, string, error) {
return runPrune(dockerCli, pruneOptions{force: true, filter: filter})
}

View File

@ -28,7 +28,7 @@ func NewRenameCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
opts.oldName = args[0]
opts.newName = args[1]
return runRename(cmd.Context(), dockerCli, &opts)
return runRename(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container rename, docker rename",
@ -38,7 +38,9 @@ func NewRenameCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runRename(ctx context.Context, dockerCli command.Cli, opts *renameOptions) error {
func runRename(dockerCli command.Cli, opts *renameOptions) error {
ctx := context.Background()
oldName := strings.TrimSpace(opts.oldName)
newName := strings.TrimSpace(opts.newName)

View File

@ -32,7 +32,7 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args
opts.timeoutChanged = cmd.Flags().Changed("time")
return runRestart(cmd.Context(), dockerCli, &opts)
return runRestart(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container restart, docker restart",
@ -46,7 +46,8 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runRestart(ctx context.Context, dockerCli command.Cli, opts *restartOptions) error {
func runRestart(dockerCli command.Cli, opts *restartOptions) error {
ctx := context.Background()
var errs []string
var timeout *int
if opts.timeoutChanged {

View File

@ -8,7 +8,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types"
"github.com/docker/docker/errdefs"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -33,7 +33,7 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args
return runRm(cmd.Context(), dockerCli, &opts)
return runRm(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container rm, docker container remove, docker rm",
@ -48,18 +48,22 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runRm(ctx context.Context, dockerCli command.Cli, opts *rmOptions) error {
func runRm(dockerCli command.Cli, opts *rmOptions) error {
ctx := context.Background()
var errs []string
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, ctrID string) error {
ctrID = strings.Trim(ctrID, "/")
if ctrID == "" {
options := types.ContainerRemoveOptions{
RemoveVolumes: opts.rmVolumes,
RemoveLinks: opts.rmLink,
Force: opts.force,
}
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, container string) error {
container = strings.Trim(container, "/")
if container == "" {
return errors.New("Container name cannot be empty")
}
return dockerCli.Client().ContainerRemove(ctx, ctrID, container.RemoveOptions{
RemoveVolumes: opts.rmVolumes,
RemoveLinks: opts.rmLink,
Force: opts.force,
})
return dockerCli.Client().ContainerRemove(ctx, container, options)
})
for _, name := range opts.containers {

View File

@ -9,7 +9,7 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types"
"github.com/docker/docker/errdefs"
"gotest.tools/v3/assert"
)
@ -29,7 +29,7 @@ func TestRemoveForce(t *testing.T) {
mutex := new(sync.Mutex)
cli := test.NewFakeCli(&fakeClient{
containerRemoveFunc: func(ctx context.Context, container string, options container.RemoveOptions) error {
containerRemoveFunc: func(ctx context.Context, container string, options types.ContainerRemoveOptions) error {
// containerRemoveFunc is called in parallel for each container
// by the remove command so append must be synchronized.
mutex.Lock()

View File

@ -12,6 +12,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/moby/sys/signal"
"github.com/moby/term"
@ -42,7 +43,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
if len(args) > 1 {
copts.Args = args[1:]
}
return runRun(cmd.Context(), dockerCli, cmd.Flags(), &options, copts)
return runRun(dockerCli, cmd.Flags(), &options, copts)
},
ValidArgsFunction: completion.ImageNames(dockerCli),
Annotations: map[string]string{
@ -89,7 +90,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
func runRun(dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
if err := validatePullOpt(ropts.pull); err != nil {
reportError(dockerCli.Err(), "run", err.Error(), true)
return cli.StatusError{StatusCode: 125}
@ -100,7 +101,7 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro
if v == nil {
newEnv = append(newEnv, k)
} else {
newEnv = append(newEnv, k+"="+*v)
newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v))
}
}
copts.env = *opts.NewListOptsRef(&newEnv, nil)
@ -114,18 +115,18 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro
reportError(dockerCli.Err(), "run", err.Error(), true)
return cli.StatusError{StatusCode: 125}
}
return runContainer(ctx, dockerCli, ropts, copts, containerCfg)
return runContainer(dockerCli, ropts, copts, containerCfg)
}
//nolint:gocyclo
func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOptions, copts *containerOptions, containerCfg *containerConfig) error {
func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptions, containerCfg *containerConfig) error {
config := containerCfg.Config
stdout, stderr := dockerCli.Out(), dockerCli.Err()
apiClient := dockerCli.Client()
client := dockerCli.Client()
config.ArgsEscaped = false
if !runOpts.detach {
if !opts.detach {
if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil {
return err
}
@ -140,17 +141,17 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
config.StdinOnce = false
}
ctx, cancelFun := context.WithCancel(ctx)
ctx, cancelFun := context.WithCancel(context.Background())
defer cancelFun()
containerID, err := createContainer(ctx, dockerCli, containerCfg, &runOpts.createOptions)
createResponse, err := createContainer(ctx, dockerCli, containerCfg, &opts.createOptions)
if err != nil {
reportError(stderr, "run", err.Error(), true)
return runStartContainerErr(err)
}
if runOpts.sigProxy {
if opts.sigProxy {
sigc := notifyAllSignals()
go ForwardAllSignals(ctx, apiClient, containerID, sigc)
go ForwardAllSignals(ctx, dockerCli, createResponse.ID, sigc)
defer signal.StopCatch(sigc)
}
@ -163,33 +164,26 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
waitDisplayID = make(chan struct{})
go func() {
defer close(waitDisplayID)
_, _ = fmt.Fprintln(stdout, containerID)
fmt.Fprintln(stdout, createResponse.ID)
}()
}
attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
if attach {
detachKeys := dockerCli.ConfigFile().DetachKeys
if runOpts.detachKeys != "" {
detachKeys = runOpts.detachKeys
if opts.detachKeys != "" {
dockerCli.ConfigFile().DetachKeys = opts.detachKeys
}
closeFn, err := attachContainer(ctx, dockerCli, containerID, &errCh, config, container.AttachOptions{
Stream: true,
Stdin: config.AttachStdin,
Stdout: config.AttachStdout,
Stderr: config.AttachStderr,
DetachKeys: detachKeys,
})
closeFn, err := attachContainer(ctx, dockerCli, &errCh, config, createResponse.ID)
if err != nil {
return err
}
defer closeFn()
}
statusChan := waitExitOrRemoved(ctx, apiClient, containerID, copts.autoRemove)
statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, copts.autoRemove)
// start the container
if err := apiClient.ContainerStart(ctx, containerID, container.StartOptions{}); err != nil {
if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
// If we have hijackedIOStreamer, we should notify
// hijackedIOStreamer we are going to exit and wait
// to avoid the terminal are not restored.
@ -207,8 +201,8 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
}
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
if err := MonitorTtySize(ctx, dockerCli, containerID, false); err != nil {
_, _ = fmt.Fprintln(stderr, "Error monitoring TTY size:", err)
if err := MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil {
fmt.Fprintln(stderr, "Error monitoring TTY size:", err)
}
}
@ -238,7 +232,15 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
return nil
}
func attachContainer(ctx context.Context, dockerCli command.Cli, containerID string, errCh *chan error, config *container.Config, options container.AttachOptions) (func(), error) {
func attachContainer(ctx context.Context, dockerCli command.Cli, errCh *chan error, config *container.Config, containerID string) (func(), error) {
options := types.ContainerAttachOptions{
Stream: true,
Stdin: config.AttachStdin,
Stdout: config.AttachStdout,
Stderr: config.AttachStderr,
DetachKeys: dockerCli.ConfigFile().DetachKeys,
}
resp, errAttach := dockerCli.Client().ContainerAttach(ctx, containerID, options)
if errAttach != nil {
return nil, errAttach
@ -248,13 +250,13 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
out, cerr io.Writer
in io.ReadCloser
)
if options.Stdin {
if config.AttachStdin {
in = dockerCli.In()
}
if options.Stdout {
if config.AttachStdout {
out = dockerCli.Out()
}
if options.Stderr {
if config.AttachStderr {
if config.Tty {
cerr = dockerCli.Out()
} else {

View File

@ -1,7 +1,6 @@
package container
import (
"context"
"errors"
"fmt"
"io"
@ -19,7 +18,7 @@ import (
)
func TestRunLabel(t *testing.T) {
fakeCLI := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
createContainerFunc: func(_ *container.Config, _ *container.HostConfig, _ *network.NetworkingConfig, _ *specs.Platform, _ string) (container.CreateResponse, error) {
return container.CreateResponse{
ID: "id",
@ -27,7 +26,7 @@ func TestRunLabel(t *testing.T) {
},
Version: "1.36",
})
cmd := NewRunCommand(fakeCLI)
cmd := NewRunCommand(cli)
cmd.SetArgs([]string{"--detach=true", "--label", "foo", "busybox"})
assert.NilError(t, cmd.Execute())
}
@ -59,7 +58,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
},
}
for _, tc := range testCases {
fakeCLI := test.NewFakeCli(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
@ -69,13 +68,13 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
return container.CreateResponse{}, fmt.Errorf("shouldn't try to pull image")
},
}, test.EnableContentTrust)
fakeCLI.SetNotaryClient(tc.notaryFunc)
cmd := NewRunCommand(fakeCLI)
cli.SetNotaryClient(tc.notaryFunc)
cmd := NewRunCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOut(io.Discard)
err := cmd.Execute()
assert.Assert(t, err != nil)
assert.Assert(t, is.Contains(fakeCLI.ErrBuffer().String(), tc.expectedError))
assert.Assert(t, is.Contains(cli.ErrBuffer().String(), tc.expectedError))
}
}
@ -98,7 +97,6 @@ func TestRunContainerImagePullPolicyInvalid(t *testing.T) {
t.Run(tc.PullPolicy, func(t *testing.T) {
dockerCli := test.NewFakeCli(&fakeClient{})
err := runRun(
context.TODO(),
dockerCli,
&pflag.FlagSet{},
&runOptions{createOptions: createOptions{pull: tc.PullPolicy}},

View File

@ -5,7 +5,7 @@ import (
"os"
gosignal "os/signal"
"github.com/docker/docker/client"
"github.com/docker/cli/cli/command"
"github.com/moby/sys/signal"
"github.com/sirupsen/logrus"
)
@ -13,7 +13,7 @@ import (
// ForwardAllSignals forwards signals to the container
//
// The channel you pass in must already be setup to receive any signals you want to forward.
func ForwardAllSignals(ctx context.Context, apiClient client.ContainerAPIClient, cid string, sigc <-chan os.Signal) {
func ForwardAllSignals(ctx context.Context, cli command.Cli, cid string, sigc <-chan os.Signal) {
var (
s os.Signal
ok bool
@ -48,7 +48,7 @@ func ForwardAllSignals(ctx context.Context, apiClient client.ContainerAPIClient,
continue
}
if err := apiClient.ContainerKill(ctx, cid, sig); err != nil {
if err := cli.Client().ContainerKill(ctx, cid, sig); err != nil {
logrus.Debugf("Error sending signal: %s", err)
}
}

View File

@ -6,6 +6,7 @@ import (
"testing"
"time"
"github.com/docker/cli/internal/test"
"github.com/moby/sys/signal"
)
@ -14,15 +15,16 @@ func TestForwardSignals(t *testing.T) {
defer cancel()
called := make(chan struct{})
apiClient := &fakeClient{containerKillFunc: func(ctx context.Context, container, signal string) error {
client := &fakeClient{containerKillFunc: func(ctx context.Context, container, signal string) error {
close(called)
return nil
}}
cli := test.NewFakeCli(client)
sigc := make(chan os.Signal)
defer close(sigc)
go ForwardAllSignals(ctx, apiClient, t.Name(), sigc)
go ForwardAllSignals(ctx, cli, t.Name(), sigc)
timer := time.NewTimer(30 * time.Second)
defer timer.Stop()

View File

@ -1,4 +1,5 @@
//go:build !windows
// +build !windows
package container

View File

@ -1,4 +1,5 @@
//go:build !windows
// +build !windows
package container
@ -9,6 +10,7 @@ import (
"testing"
"time"
"github.com/docker/cli/internal/test"
"golang.org/x/sys/unix"
"gotest.tools/v3/assert"
)
@ -22,17 +24,18 @@ func TestIgnoredSignals(t *testing.T) {
defer cancel()
var called bool
apiClient := &fakeClient{containerKillFunc: func(ctx context.Context, container, signal string) error {
client := &fakeClient{containerKillFunc: func(ctx context.Context, container, signal string) error {
called = true
return nil
}}
cli := test.NewFakeCli(client)
sigc := make(chan os.Signal)
defer close(sigc)
done := make(chan struct{})
go func() {
ForwardAllSignals(ctx, apiClient, t.Name(), sigc)
ForwardAllSignals(ctx, cli, t.Name(), sigc)
close(done)
}()

View File

@ -10,7 +10,6 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/moby/sys/signal"
"github.com/moby/term"
"github.com/pkg/errors"
@ -28,9 +27,7 @@ type StartOptions struct {
Containers []string
}
// NewStartOptions creates a new StartOptions.
//
// Deprecated: create a new [StartOptions] directly.
// NewStartOptions creates a new StartOptions
func NewStartOptions() StartOptions {
return StartOptions{}
}
@ -45,7 +42,7 @@ func NewStartCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.Containers = args
return RunStart(cmd.Context(), dockerCli, &opts)
return RunStart(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container start, docker start",
@ -72,12 +69,11 @@ func NewStartCommand(dockerCli command.Cli) *cobra.Command {
// RunStart executes a `start` command
//
//nolint:gocyclo
func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) error {
ctx, cancelFun := context.WithCancel(ctx)
func RunStart(dockerCli command.Cli, opts *StartOptions) error {
ctx, cancelFun := context.WithCancel(context.Background())
defer cancelFun()
switch {
case opts.Attach || opts.OpenStdin:
if opts.Attach || opts.OpenStdin {
// We're going to attach to a container.
// 1. Ensure we only have one container.
if len(opts.Containers) > 1 {
@ -85,8 +81,8 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
}
// 2. Attach to the container.
ctr := opts.Containers[0]
c, err := dockerCli.Client().ContainerInspect(ctx, ctr)
container := opts.Containers[0]
c, err := dockerCli.Client().ContainerInspect(ctx, container)
if err != nil {
return err
}
@ -94,21 +90,20 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
// We always use c.ID instead of container to maintain consistency during `docker start`
if !c.Config.Tty {
sigc := notifyAllSignals()
go ForwardAllSignals(ctx, dockerCli.Client(), c.ID, sigc)
go ForwardAllSignals(ctx, dockerCli, c.ID, sigc)
defer signal.StopCatch(sigc)
}
detachKeys := dockerCli.ConfigFile().DetachKeys
if opts.DetachKeys != "" {
detachKeys = opts.DetachKeys
dockerCli.ConfigFile().DetachKeys = opts.DetachKeys
}
options := container.AttachOptions{
options := types.ContainerAttachOptions{
Stream: true,
Stdin: opts.OpenStdin && c.Config.OpenStdin,
Stdout: true,
Stderr: true,
DetachKeys: detachKeys,
DetachKeys: dockerCli.ConfigFile().DetachKeys,
}
var in io.ReadCloser
@ -147,14 +142,14 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
// 3. We should open a channel for receiving status code of the container
// no matter it's detached, removed on daemon side(--rm) or exit normally.
statusChan := waitExitOrRemoved(ctx, dockerCli.Client(), c.ID, c.HostConfig.AutoRemove)
// 4. Start the container.
err = dockerCli.Client().ContainerStart(ctx, c.ID, container.StartOptions{
statusChan := waitExitOrRemoved(ctx, dockerCli, c.ID, c.HostConfig.AutoRemove)
startOptions := types.ContainerStartOptions{
CheckpointID: opts.Checkpoint,
CheckpointDir: opts.CheckpointDir,
})
if err != nil {
}
// 4. Start the container.
if err := dockerCli.Client().ContainerStart(ctx, c.ID, startOptions); err != nil {
cancelFun()
<-cErr
if c.HostConfig.AutoRemove {
@ -181,32 +176,35 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
if status := <-statusChan; status != 0 {
return cli.StatusError{StatusCode: status}
}
return nil
case opts.Checkpoint != "":
} else if opts.Checkpoint != "" {
if len(opts.Containers) > 1 {
return errors.New("you cannot restore multiple containers at once")
}
ctr := opts.Containers[0]
return dockerCli.Client().ContainerStart(ctx, ctr, container.StartOptions{
container := opts.Containers[0]
startOptions := types.ContainerStartOptions{
CheckpointID: opts.Checkpoint,
CheckpointDir: opts.CheckpointDir,
})
default:
}
return dockerCli.Client().ContainerStart(ctx, container, startOptions)
} else {
// We're not going to attach to anything.
// Start as many containers as we want.
return startContainersWithoutAttachments(ctx, dockerCli, opts.Containers)
}
return nil
}
func startContainersWithoutAttachments(ctx context.Context, dockerCli command.Cli, containers []string) error {
var failedContainers []string
for _, ctr := range containers {
if err := dockerCli.Client().ContainerStart(ctx, ctr, container.StartOptions{}); err != nil {
for _, container := range containers {
if err := dockerCli.Client().ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil {
fmt.Fprintln(dockerCli.Err(), err)
failedContainers = append(failedContainers, ctr)
failedContainers = append(failedContainers, container)
continue
}
fmt.Fprintln(dockerCli.Out(), ctr)
fmt.Fprintln(dockerCli.Out(), container)
}
if len(failedContainers) > 0 {

View File

@ -14,101 +14,89 @@ import (
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// StatsOptions defines options for [RunStats].
type StatsOptions struct {
// All allows including both running and stopped containers. The default
// is to only include running containers.
All bool
// NoStream disables streaming stats. If enabled, stats are collected once,
// and the result is printed.
NoStream bool
// NoTrunc disables truncating the output. The default is to truncate
// output such as container-IDs.
NoTrunc bool
// Format is a custom template to use for presenting the stats.
// Refer to [flagsHelper.FormatHelp] for accepted formats.
Format string
// Containers is the list of container names or IDs to include in the stats.
// If empty, all containers are included. It is mutually exclusive with the
// Filters option, and an error is produced if both are set.
Containers []string
// Filters provides optional filters to filter the list of containers and their
// associated container-events to include in the stats if no list of containers
// is set. If no filter is provided, all containers are included. Filters and
// Containers are currently mutually exclusive, and setting both options
// produces an error.
//
// These filters are used both to collect the initial list of containers and
// to refresh the list of containers based on container-events, accepted
// filters are limited to the intersection of filters accepted by "events"
// and "container list".
//
// Currently only "label" / "label=value" filters are accepted. Additional
// filter options may be added in future (within the constraints described
// above), but may require daemon-side validation as the list of accepted
// filters can differ between daemon- and API versions.
Filters *filters.Args
type statsOptions struct {
all bool
noStream bool
noTrunc bool
format string
containers []string
}
// NewStatsCommand creates a new [cobra.Command] for "docker stats".
func NewStatsCommand(dockerCLI command.Cli) *cobra.Command {
options := StatsOptions{}
// NewStatsCommand creates a new cobra.Command for `docker stats`
func NewStatsCommand(dockerCli command.Cli) *cobra.Command {
var opts statsOptions
cmd := &cobra.Command{
Use: "stats [OPTIONS] [CONTAINER...]",
Short: "Display a live stream of container(s) resource usage statistics",
Args: cli.RequiresMinArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
options.Containers = args
return RunStats(cmd.Context(), dockerCLI, &options)
opts.containers = args
return runStats(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container stats, docker stats",
},
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
}
flags := cmd.Flags()
flags.BoolVarP(&options.All, "all", "a", false, "Show all containers (default shows just running)")
flags.BoolVar(&options.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result")
flags.BoolVar(&options.NoTrunc, "no-trunc", false, "Do not truncate output")
flags.StringVar(&options.Format, "format", "", flagsHelper.FormatHelp)
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
flags.BoolVar(&opts.noStream, "no-stream", false, "Disable streaming stats and only pull the first result")
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
flags.StringVar(&opts.format, "format", "", flagsHelper.FormatHelp)
return cmd
}
// acceptedStatsFilters is the list of filters accepted by [RunStats] (through
// the [StatsOptions.Filters] option).
//
// TODO(thaJeztah): don't hard-code the list of accept filters, and expand
// to the intersection of filters accepted by both "container list" and
// "system events". Validating filters may require an initial API call
// to both endpoints ("container list" and "system events").
var acceptedStatsFilters = map[string]bool{
"label": true,
}
// RunStats displays a live stream of resource usage statistics for one or more containers.
// runStats displays a live stream of resource usage statistics for one or more containers.
// This shows real-time information on CPU usage, memory usage, and network I/O.
//
//nolint:gocyclo
func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions) error {
apiClient := dockerCLI.Client()
func runStats(dockerCli command.Cli, opts *statsOptions) error {
showAll := len(opts.containers) == 0
closeChan := make(chan error)
ctx := context.Background()
// monitorContainerEvents watches for container creation and removal (only
// used when calling `docker stats` without arguments).
monitorContainerEvents := func(started chan<- struct{}, c chan events.Message, stopped <-chan struct{}) {
f := filters.NewArgs()
f.Add("type", "container")
options := types.EventsOptions{
Filters: f,
}
eventq, errq := dockerCli.Client().Events(ctx, options)
// Whether we successfully subscribed to eventq or not, we can now
// unblock the main goroutine.
close(started)
defer close(c)
for {
select {
case <-stopped:
return
case event := <-eventq:
c <- event
case err := <-errq:
closeChan <- err
return
}
}
}
// Get the daemonOSType if not set already
if daemonOSType == "" {
sv, err := apiClient.ServerVersion(ctx)
svctx := context.Background()
sv, err := dockerCli.Client().ServerVersion(svctx)
if err != nil {
return err
}
@ -117,82 +105,57 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
// waitFirst is a WaitGroup to wait first stat data's reach for each container
waitFirst := &sync.WaitGroup{}
// closeChan is a non-buffered channel used to collect errors from goroutines.
closeChan := make(chan error)
cStats := stats{}
showAll := len(options.Containers) == 0
cStats := stats{}
// getContainerList simulates creation event for all previously existing
// containers (only used when calling `docker stats` without arguments).
getContainerList := func() {
options := types.ContainerListOptions{
All: opts.all,
}
cs, err := dockerCli.Client().ContainerList(ctx, options)
if err != nil {
closeChan <- err
}
for _, container := range cs {
s := NewStats(container.ID[:12])
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
}
}
}
if showAll {
// If no names were specified, start a long-running goroutine which
// If no names were specified, start a long running goroutine which
// monitors container events. We make sure we're subscribed before
// retrieving the list of running containers to avoid a race where we
// would "miss" a creation.
started := make(chan struct{})
if options.Filters == nil {
f := filters.NewArgs()
options.Filters = &f
}
if err := options.Filters.Validate(acceptedStatsFilters); err != nil {
return err
}
eh := command.InitEventHandler()
if options.All {
eh.Handle(events.ActionCreate, func(e events.Message) {
s := NewStats(e.Actor.ID[:12])
eh.Handle("create", func(e events.Message) {
if opts.all {
s := NewStats(e.ID[:12])
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
}
})
}
eh.Handle(events.ActionStart, func(e events.Message) {
s := NewStats(e.Actor.ID[:12])
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
}
})
if !options.All {
eh.Handle(events.ActionDie, func(e events.Message) {
cStats.remove(e.Actor.ID[:12])
})
}
// monitorContainerEvents watches for container creation and removal (only
// used when calling `docker stats` without arguments).
monitorContainerEvents := func(started chan<- struct{}, c chan events.Message, stopped <-chan struct{}) {
// Create a copy of the custom filters so that we don't mutate
// the original set of filters. Custom filters are used both
// to list containers and to filter events, but the "type" filter
// is not valid for filtering containers.
f := options.Filters.Clone()
f.Add("type", string(events.ContainerEventType))
eventChan, errChan := apiClient.Events(ctx, types.EventsOptions{
Filters: f,
})
// Whether we successfully subscribed to eventChan or not, we can now
// unblock the main goroutine.
close(started)
defer close(c)
for {
select {
case <-stopped:
return
case event := <-eventChan:
c <- event
case err := <-errChan:
closeChan <- err
return
}
eh.Handle("start", func(e events.Message) {
s := NewStats(e.ID[:12])
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
}
}
})
eh.Handle("die", func(e events.Message) {
if !opts.all {
cStats.remove(e.ID[:12])
}
})
eventChan := make(chan events.Message)
go eh.Watch(eventChan)
@ -201,42 +164,20 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
defer close(stopped)
<-started
// Fetch the initial list of containers and collect stats for them.
// After the initial list was collected, we start listening for events
// to refresh the list of containers.
cs, err := apiClient.ContainerList(ctx, container.ListOptions{
All: options.All,
Filters: *options.Filters,
})
if err != nil {
return err
}
for _, ctr := range cs {
s := NewStats(ctr.ID[:12])
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
}
}
// Start a short-lived goroutine to retrieve the initial list of
// containers.
getContainerList()
// make sure each container get at least one valid stat data
waitFirst.Wait()
} else {
// TODO(thaJeztah): re-implement options.Containers as a filter so that
// only a single code-path is needed, and custom filters can be combined
// with a list of container names/IDs.
if options.Filters != nil && options.Filters.Len() > 0 {
return fmt.Errorf("filtering is not supported when specifying a list of containers")
}
// Create the list of containers, and start collecting stats for all
// containers passed.
for _, ctr := range options.Containers {
s := NewStats(ctr)
// Artificially send creation events for the containers we were asked to
// monitor (same code path than we use when monitoring all containers).
for _, name := range opts.containers {
s := NewStats(name)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
}
}
@ -259,22 +200,22 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
}
}
format := options.Format
format := opts.format
if len(format) == 0 {
if len(dockerCLI.ConfigFile().StatsFormat) > 0 {
format = dockerCLI.ConfigFile().StatsFormat
if len(dockerCli.ConfigFile().StatsFormat) > 0 {
format = dockerCli.ConfigFile().StatsFormat
} else {
format = formatter.TableFormatKey
}
}
statsCtx := formatter.Context{
Output: dockerCLI.Out(),
Output: dockerCli.Out(),
Format: NewStatsFormat(format, daemonOSType),
}
cleanScreen := func() {
if !options.NoStream {
_, _ = fmt.Fprint(dockerCLI.Out(), "\033[2J")
_, _ = fmt.Fprint(dockerCLI.Out(), "\033[H")
if !opts.noStream {
fmt.Fprint(dockerCli.Out(), "\033[2J")
fmt.Fprint(dockerCli.Out(), "\033[H")
}
}
@ -283,28 +224,28 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
defer ticker.Stop()
for range ticker.C {
cleanScreen()
var ccStats []StatsEntry
ccstats := []StatsEntry{}
cStats.mu.RLock()
for _, c := range cStats.cs {
ccStats = append(ccStats, c.GetStatistics())
ccstats = append(ccstats, c.GetStatistics())
}
cStats.mu.RUnlock()
if err = statsFormatWrite(statsCtx, ccStats, daemonOSType, !options.NoTrunc); err != nil {
if err = statsFormatWrite(statsCtx, ccstats, daemonOSType, !opts.noTrunc); err != nil {
break
}
if len(cStats.cs) == 0 && !showAll {
break
}
if options.NoStream {
if opts.noStream {
break
}
select {
case err, ok := <-closeChan:
if ok {
if err != nil {
// Suppress "unexpected EOF" errors in the CLI so that
// it shuts down cleanly when the daemon restarts.
if errors.Is(err, io.ErrUnexpectedEOF) {
// this is suppressing "unexpected EOF" in the cli when the
// daemon restarts so it shutdowns cleanly
if err == io.ErrUnexpectedEOF {
return nil
}
return err

View File

@ -206,9 +206,9 @@ func calculateBlockIO(blkio types.BlkioStats) (uint64, uint64) {
}
switch bioEntry.Op[0] {
case 'r', 'R':
blkRead += bioEntry.Value
blkRead = blkRead + bioEntry.Value
case 'w', 'W':
blkWrite += bioEntry.Value
blkWrite = blkWrite + bioEntry.Value
}
}
return blkRead, blkWrite

View File

@ -32,7 +32,7 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args
opts.timeoutChanged = cmd.Flags().Changed("time")
return runStop(cmd.Context(), dockerCli, &opts)
return runStop(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container stop, docker stop",
@ -46,13 +46,13 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runStop(ctx context.Context, dockerCli command.Cli, opts *stopOptions) error {
func runStop(dockerCli command.Cli, opts *stopOptions) error {
var timeout *int
if opts.timeoutChanged {
timeout = &opts.timeout
}
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, id string) error {
errChan := parallelOperation(context.Background(), opts.containers, func(ctx context.Context, id string) error {
return dockerCli.Client().ContainerStop(ctx, id, container.StopOptions{
Signal: opts.signal,
Timeout: timeout,

View File

@ -29,7 +29,7 @@ func NewTopCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
opts.container = args[0]
opts.args = args[1:]
return runTop(cmd.Context(), dockerCli, &opts)
return runTop(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container top, docker top",
@ -43,7 +43,9 @@ func NewTopCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runTop(ctx context.Context, dockerCli command.Cli, opts *topOptions) error {
func runTop(dockerCli command.Cli, opts *topOptions) error {
ctx := context.Background()
procList, err := dockerCli.Client().ContainerTop(ctx, opts.container, opts.args)
if err != nil {
return err

View File

@ -9,28 +9,28 @@ import (
"time"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/moby/sys/signal"
"github.com/sirupsen/logrus"
)
// resizeTtyTo resizes tty to specific height and width
func resizeTtyTo(ctx context.Context, apiClient client.ContainerAPIClient, id string, height, width uint, isExec bool) error {
func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id string, height, width uint, isExec bool) error {
if height == 0 && width == 0 {
return nil
}
options := container.ResizeOptions{
options := types.ResizeOptions{
Height: height,
Width: width,
}
var err error
if isExec {
err = apiClient.ContainerExecResize(ctx, id, options)
err = client.ContainerExecResize(ctx, id, options)
} else {
err = apiClient.ContainerResize(ctx, id, options)
err = client.ContainerResize(ctx, id, options)
}
if err != nil {

View File

@ -7,7 +7,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -15,7 +15,7 @@ import (
func TestInitTtySizeErrors(t *testing.T) {
expectedError := "failed to resize tty, using default size\n"
fakeContainerExecResizeFunc := func(id string, options container.ResizeOptions) error {
fakeContainerExecResizeFunc := func(id string, options types.ResizeOptions) error {
return errors.Errorf("Error response from daemon: no such exec")
}
fakeResizeTtyFunc := func(ctx context.Context, cli command.Cli, id string, isExec bool) error {

View File

@ -27,7 +27,7 @@ func NewUnpauseCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args
return runUnpause(cmd.Context(), dockerCli, &opts)
return runUnpause(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container unpause, docker unpause",
@ -39,7 +39,9 @@ func NewUnpauseCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runUnpause(ctx context.Context, dockerCli command.Cli, opts *unpauseOptions) error {
func runUnpause(dockerCli command.Cli, opts *unpauseOptions) error {
ctx := context.Background()
var errs []string
errChan := parallelOperation(ctx, opts.containers, dockerCli.Client().ContainerUnpause)
for _, container := range opts.containers {

View File

@ -47,7 +47,7 @@ func NewUpdateCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
options.containers = args
options.nFlag = cmd.Flags().NFlag()
return runUpdate(cmd.Context(), dockerCli, &options)
return runUpdate(dockerCli, &options)
},
Annotations: map[string]string{
"aliases": "docker container update, docker update",
@ -86,7 +86,7 @@ func NewUpdateCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runUpdate(ctx context.Context, dockerCli command.Cli, options *updateOptions) error {
func runUpdate(dockerCli command.Cli, options *updateOptions) error {
var err error
if options.nFlag == 0 {
@ -126,6 +126,8 @@ func runUpdate(ctx context.Context, dockerCli command.Cli, options *updateOption
RestartPolicy: restartPolicy,
}
ctx := context.Background()
var (
warns []string
errs []string

View File

@ -4,16 +4,16 @@ import (
"context"
"strconv"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/sirupsen/logrus"
)
func waitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containerID string, waitRemove bool) <-chan int {
func waitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID string, waitRemove bool) <-chan int {
if len(containerID) == 0 {
// containerID can never be empty
panic("Internal Error: waitExitOrRemoved needs a containerID as parameter")
@ -22,8 +22,8 @@ func waitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containe
// Older versions used the Events API, and even older versions did not
// support server-side removal. This legacyWaitExitOrRemoved method
// preserves that old behavior and any issues it may have.
if versions.LessThan(apiClient.ClientVersion(), "1.30") {
return legacyWaitExitOrRemoved(ctx, apiClient, containerID, waitRemove)
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") {
return legacyWaitExitOrRemoved(ctx, dockerCli, containerID, waitRemove)
}
condition := container.WaitConditionNextExit
@ -31,7 +31,7 @@ func waitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containe
condition = container.WaitConditionRemoved
}
resultC, errC := apiClient.ContainerWait(ctx, containerID, condition)
resultC, errC := dockerCli.Client().ContainerWait(ctx, containerID, condition)
statusC := make(chan int)
go func() {
@ -52,7 +52,7 @@ func waitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containe
return statusC
}
func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containerID string, waitRemove bool) <-chan int {
func legacyWaitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID string, waitRemove bool) <-chan int {
var removeErr error
statusChan := make(chan int)
exitCode := 125
@ -65,7 +65,7 @@ func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, co
Filters: f,
}
eventCtx, cancel := context.WithCancel(ctx)
eventq, errq := apiClient.Events(eventCtx, options)
eventq, errq := dockerCli.Client().Events(eventCtx, options)
eventProcessor := func(e events.Message) bool {
stopProcessing := false
@ -81,16 +81,18 @@ func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, co
}
if !waitRemove {
stopProcessing = true
} else if versions.LessThan(apiClient.ClientVersion(), "1.25") {
} else {
// If we are talking to an older daemon, `AutoRemove` is not supported.
// We need to fall back to the old behavior, which is client-side removal
go func() {
removeErr = apiClient.ContainerRemove(ctx, containerID, container.RemoveOptions{RemoveVolumes: true})
if removeErr != nil {
logrus.Errorf("error removing container: %v", removeErr)
cancel() // cancel the event Q
}
}()
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.25") {
go func() {
removeErr = dockerCli.Client().ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{RemoveVolumes: true})
if removeErr != nil {
logrus.Errorf("error removing container: %v", removeErr)
cancel() // cancel the event Q
}
}()
}
}
case "detach":
exitCode = 0
@ -127,7 +129,7 @@ func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, co
return statusChan
}
func parallelOperation(ctx context.Context, containers []string, op func(ctx context.Context, containerID string) error) chan error {
func parallelOperation(ctx context.Context, containers []string, op func(ctx context.Context, container string) error) chan error {
if len(containers) == 0 {
return nil
}

View File

@ -2,12 +2,13 @@ package container
import (
"context"
"fmt"
"strings"
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@ -23,7 +24,7 @@ func waitFn(cid string) (<-chan container.WaitResponse, <-chan error) {
res.StatusCode = 42
resC <- res
case strings.Contains(cid, "non-existent"):
err := fmt.Errorf("no such container: %v", cid)
err := errors.Errorf("no such container: %v", cid)
errC <- err
case strings.Contains(cid, "wait-error"):
res.Error = &container.WaitExitError{Message: "removal failed"}
@ -60,7 +61,7 @@ func TestWaitExitOrRemoved(t *testing.T) {
},
}
client := &fakeClient{waitFunc: waitFn, Version: api.DefaultVersion}
client := test.NewFakeCli(&fakeClient{waitFunc: waitFn, Version: api.DefaultVersion})
for _, testcase := range testcases {
statusC := waitExitOrRemoved(context.Background(), client, testcase.cid, true)
exitCode := <-statusC

View File

@ -26,7 +26,7 @@ func NewWaitCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args
return runWait(cmd.Context(), dockerCli, &opts)
return runWait(dockerCli, &opts)
},
Annotations: map[string]string{
"aliases": "docker container wait, docker wait",
@ -37,7 +37,9 @@ func NewWaitCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runWait(ctx context.Context, dockerCli command.Cli, opts *waitOptions) error {
func runWait(dockerCli command.Cli, opts *waitOptions) error {
ctx := context.Background()
var errs []string
for _, container := range opts.containers {
resultC, errC := dockerCli.Client().ContainerWait(ctx, container, "")

View File

@ -1,6 +1,3 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.19
package command
import (
@ -13,12 +10,12 @@ import (
// DockerContext is a typed representation of what we put in Context metadata
type DockerContext struct {
Description string
AdditionalFields map[string]any
AdditionalFields map[string]interface{}
}
// MarshalJSON implements custom JSON marshalling
func (dc DockerContext) MarshalJSON() ([]byte, error) {
s := map[string]any{}
s := map[string]interface{}{}
if dc.Description != "" {
s["Description"] = dc.Description
}
@ -32,7 +29,7 @@ func (dc DockerContext) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements custom JSON marshalling
func (dc *DockerContext) UnmarshalJSON(payload []byte) error {
var data map[string]any
var data map[string]interface{}
if err := json.Unmarshal(payload, &data); err != nil {
return err
}
@ -42,7 +39,7 @@ func (dc *DockerContext) UnmarshalJSON(payload []byte) error {
dc.Description = v.(string)
default:
if dc.AdditionalFields == nil {
dc.AdditionalFields = make(map[string]any)
dc.AdditionalFields = make(map[string]interface{})
}
dc.AdditionalFields[k] = v
}

View File

@ -1,6 +1,3 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.19
package context
import (
@ -39,7 +36,7 @@ func longCreateDescription() string {
return buf.String()
}
func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
opts := &CreateOptions{}
cmd := &cobra.Command{
Use: "create [OPTIONS] CONTEXT",
@ -47,50 +44,61 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.Name = args[0]
return RunCreate(dockerCLI, opts)
return RunCreate(dockerCli, opts)
},
Long: longCreateDescription(),
ValidArgsFunction: completion.NoComplete,
}
flags := cmd.Flags()
flags.StringVar(&opts.Description, "description", "", "Description of the context")
flags.String(
"default-stack-orchestrator", "",
`Default orchestrator for stack operations to use with this context ("swarm", "kubernetes", "all")`,
)
flags.SetAnnotation("default-stack-orchestrator", "deprecated", nil)
flags.SetAnnotation("default-stack-orchestrator", "deprecated", nil)
flags.MarkDeprecated("default-stack-orchestrator", "option will be ignored")
flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint")
flags.StringToString("kubernetes", nil, "set the kubernetes endpoint")
flags.SetAnnotation("kubernetes", "kubernetes", nil)
flags.SetAnnotation("kubernetes", "deprecated", nil)
flags.MarkDeprecated("kubernetes", "option will be ignored")
flags.StringVar(&opts.From, "from", "", "create context from a named context")
return cmd
}
// RunCreate creates a Docker context
func RunCreate(dockerCLI command.Cli, o *CreateOptions) error {
s := dockerCLI.ContextStore()
func RunCreate(cli command.Cli, o *CreateOptions) error {
s := cli.ContextStore()
err := checkContextNameForCreation(s, o.Name)
if err != nil {
return err
}
switch {
case o.From == "" && o.Docker == nil:
err = createFromExistingContext(s, dockerCLI.CurrentContext(), o)
err = createFromExistingContext(s, cli.CurrentContext(), o)
case o.From != "":
err = createFromExistingContext(s, o.From, o)
default:
err = createNewContext(s, o)
err = createNewContext(o, cli, s)
}
if err == nil {
fmt.Fprintln(dockerCLI.Out(), o.Name)
fmt.Fprintf(dockerCLI.Err(), "Successfully created context %q\n", o.Name)
fmt.Fprintln(cli.Out(), o.Name)
fmt.Fprintf(cli.Err(), "Successfully created context %q\n", o.Name)
}
return err
}
func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
func createNewContext(o *CreateOptions, cli command.Cli, s store.Writer) error {
if o.Docker == nil {
return errors.New("docker endpoint configuration is required")
}
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(contextStore, o.Docker)
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(cli, o.Docker)
if err != nil {
return errors.Wrap(err, "unable to create docker endpoint config")
}
contextMetadata := store.Metadata{
Endpoints: map[string]any{
Endpoints: map[string]interface{}{
docker.DockerEndpoint: dockerEP,
},
Metadata: command.DockerContext{
@ -107,10 +115,10 @@ func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
if err := validateEndpoints(contextMetadata); err != nil {
return err
}
if err := contextStore.CreateOrUpdate(contextMetadata); err != nil {
if err := s.CreateOrUpdate(contextMetadata); err != nil {
return err
}
return contextStore.ResetTLSMaterial(o.Name, &contextTLSData)
return s.ResetTLSMaterial(o.Name, &contextTLSData)
}
func checkContextNameForCreation(s store.Reader, name string) error {

View File

@ -1,6 +1,3 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.19
package context
import (
@ -19,15 +16,15 @@ func makeFakeCli(t *testing.T, opts ...func(*test.FakeCli)) *test.FakeCli {
t.Helper()
dir := t.TempDir()
storeConfig := store.NewConfig(
func() any { return &command.DockerContext{} },
store.EndpointTypeGetter(docker.DockerEndpoint, func() any { return &docker.EndpointMeta{} }),
func() interface{} { return &command.DockerContext{} },
store.EndpointTypeGetter(docker.DockerEndpoint, func() interface{} { return &docker.EndpointMeta{} }),
)
contextStore := &command.ContextStoreWithDefault{
store := &command.ContextStoreWithDefault{
Store: store.New(dir, storeConfig),
Resolver: func() (*command.DefaultContext, error) {
return &command.DefaultContext{
Meta: store.Metadata{
Endpoints: map[string]any{
Endpoints: map[string]interface{}{
docker.DockerEndpoint: docker.EndpointMeta{
Host: "unix:///var/run/docker.sock",
},
@ -45,7 +42,7 @@ func makeFakeCli(t *testing.T, opts ...func(*test.FakeCli)) *test.FakeCli {
for _, o := range opts {
o(result)
}
result.SetContextStore(contextStore)
result.SetContextStore(store)
return result
}
@ -107,7 +104,6 @@ func TestCreate(t *testing.T) {
}
func assertContextCreateLogging(t *testing.T, cli *test.FakeCli, n string) {
t.Helper()
assert.Equal(t, n+"\n", cli.OutBuffer().String())
assert.Equal(t, fmt.Sprintf("Successfully created context %q\n", n), cli.ErrBuffer().String())
}

View File

@ -19,14 +19,13 @@ type ExportOptions struct {
}
func newExportCommand(dockerCli command.Cli) *cobra.Command {
return &cobra.Command{
opts := &ExportOptions{}
cmd := &cobra.Command{
Use: "export [OPTIONS] CONTEXT [FILE|-]",
Short: "Export a context to a tar archive FILE or a tar stream on STDOUT.",
Args: cli.RequiresRangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
opts := &ExportOptions{
ContextName: args[0],
}
opts.ContextName = args[0]
if len(args) == 2 {
opts.Dest = args[1]
} else {
@ -35,6 +34,13 @@ func newExportCommand(dockerCli command.Cli) *cobra.Command {
return RunExport(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.Bool("kubeconfig", false, "Export as a kubeconfig file")
flags.MarkDeprecated("kubeconfig", "option will be ignored")
flags.SetAnnotation("kubeconfig", "kubernetes", nil)
flags.SetAnnotation("kubeconfig", "deprecated", nil)
return cmd
}
func writeTo(dockerCli command.Cli, reader io.Reader, dest string) error {

View File

@ -1,6 +1,3 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.19
package context
import (
@ -43,7 +40,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
}
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
getRefFunc := func(ref string) (any, []byte, error) {
getRefFunc := func(ref string) (interface{}, []byte, error) {
c, err := dockerCli.ContextStore().GetMetadata(ref)
if err != nil {
return nil, nil, err

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