Compare commits

...
231 Commits
Author SHA1 Message Date
Paweł GronowskiandGitHub c2be9ccfc3 Merge pull request #6887 from thaJeztah/29.x_backport_pin_actions
[29.x backport] ci: pin actions to digests
2026-03-25 15:22:32 +01:00
Sebastiaan van Stijn 0da6a5121f ci: pin actions to digests
As a follow-up, we should use the full version (major.minor.patch).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 97b9e04a94)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 15:06:23 +01:00
Sebastiaan van StijnandGitHub 6b3ca8fc04 Merge pull request #6884 from thaJeztah/29.x_backport_missing_buildtags
[29.x backport] cli/command: add missing "go:build" comments
2026-03-25 15:04:52 +01:00
Paweł GronowskiandGitHub f47603c290 Merge pull request #6885 from thaJeztah/29.x_backport_update_actions
[29.x backport] update actions
2026-03-25 14:23:37 +01:00
dependabot[bot]andSebastiaan van Stijn 9709c8fe08 build(deps): bump docker/metadata-action from 5 to 6
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 39d676c72d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 14:16:00 +01:00
dependabot[bot]andSebastiaan van Stijn af45a23fa9 build(deps): bump docker/bake-action from 6 to 7
Bumps [docker/bake-action](https://github.com/docker/bake-action) from 6 to 7.
- [Release notes](https://github.com/docker/bake-action/releases)
- [Commits](https://github.com/docker/bake-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/bake-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 6453c4c3a7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 14:15:48 +01:00
Paweł GronowskiandGitHub bc97f5ac19 Merge pull request #6883 from thaJeztah/29.x_backport_update_go1.25.8
[29.x backport] update to go1.25.8
2026-03-25 14:14:46 +01:00
Sebastiaan van Stijn 6d71967120 cli/command: add missing "go:build" comments
- commit e8dc2fce32 modernized loops to
  range over int, which requires go1.22 or later.
- commit 85ebca52fd modernized code to
  use stdlib min/max, which requires go1.21 or later.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9a471180cb)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 14:11:15 +01:00
Sebastiaan van Stijn 9b51892b80 update to go1.25.8
go1.25.8 (released 2026-03-05) includes security fixes to the html/template,
net/url, and os packages, as well as bug fixes to the go command, the compiler,
and the os package. See the Go 1.25.8 milestone on our issue tracker for details.

- 1.25.8 https://github.com/golang/go/issues?q=milestone%3AGo1.25.8+label%3ACherryPickApproved
- diff: https://github.com/golang/go/compare/go1.25.7...go1.25.8
- 1.26.1 https://github.com/golang/go/issues?q=milestone%3AGo1.26.1+label%3ACherryPickApproved
- diff: https://github.com/golang/go/compare/go1.26.0...go1.26.1

---

We have just released Go versions 1.26.1 and 1.25.8, minor point releases.

These releases include 5 security fixes following the security policy:

crypto/x509: incorrect enforcement of email constraints

- When verifying a certificate chain which contains a certificate containing
  multiple email address constraints (composed of the full email address) which
  share common local portions (the portion of the address before the '@'
  character) but different domain portions (the portion of the address after the
  '@' character), these constraints will not be properly applied, and only the
  last constraint will be considered.

  This can allow certificates in the chain containing email addresses which are
  either not permitted or excluded by the relevant constraints to be returned by
  calls to Certificate.Verify. Since the name constraint checks happen after chain
  building is complete, this only applies to certificate chains which chain to
  trusted roots (root certificates either in VerifyOptions.Roots or in the system
  root certificate pool), requiring a trusted CA to issue certificates containing
  either not permitted or excluded email addresses.

  This issue only affects Go 1.26.

  Thanks to Jakub Ciolek for reporting this issue.

  This is CVE-2026-27137 and Go issue https://go.dev/issue/77952.

- crypto/x509: panic in name constraint checking for malformed certificates

  Certificate verification can panic when a certificate in the chain has an empty
  DNS name and another certificate in the chain has excluded name constraints.
  This can crash programs that are either directly verifying X.509 certificate
  chains, or those that use TLS.

  Since the name constraint checks happen after chain building is complete, this
  only applies to certificate chains which chain to trusted roots (root
  certificates either in VerifyOptions.Roots or in the system root certificate
  pool), requiring a trusted CA to issue certificates containing malformed DNS
  names.

  This issue only affects Go 1.26.

  Thanks to Jakub Ciolek for reporting this issue.

  This is CVE-2026-27138 and Go issue https://go.dev/issue/77953.

- html/template: URLs in meta content attribute actions are not escaped

  Actions which insert URLs into the content attribute of HTML meta tags are not
  escaped. This can allow XSS if the meta tag also has an http-equiv attribute
  with the value "refresh".

  A new GODEBUG setting has been added, htmlmetacontenturlescape, which can be
  used to disable escaping URLs in actions in the meta content attribute which
  follow "url=" by setting htmlmetacontenturlescape=0.

  This is CVE-2026-27142 and Go issue https://go.dev/issue/77954.

- net/url: reject IPv6 literal not at start of host

  The Go standard library function net/url.Parse insufficiently
  validated the host/authority component and accepted some invalid URLs
  by effectively treating garbage before an IP-literal as ignorable.
  The function should have rejected this as invalid.

  To prevent this behavior, net/url.Parse now rejects IPv6 literals
  that do not appear at the start of the host subcomponent of a URL.

  Thanks to Masaki Hara (https://github.com/qnighy) of Wantedly.

  This is CVE-2026-25679 and Go issue https://go.dev/issue/77578.

- os: FileInfo can escape from a Root

  On Unix platforms, when listing the contents of a directory using
  File.ReadDir or File.Readdir the returned FileInfo could reference
  a file outside of the Root in which the File was opened.

  The contents of the FileInfo were populated using the lstat system
  call, which takes the path to the file as a parameter. If a component
  of the full path of the file described by the FileInfo is replaced with
  a symbolic link, the target of the lstat can be directed to another
  location on the filesystem.

  The impact of this escape is limited to reading metadata provided by
  lstat from arbitrary locations on the filesystem. This could be used
  to probe for the presence or absence of files as well as gleaning
  metadata like file sizes, but does not permit reading or writing files
  outside the root.

  The FileInfo is now populated using fstatat.

  Thank you to Miloslav Trmač of Red Hat for reporting this issue.

  This is CVE-2026-27139 and Go issue https://go.dev/issue/77827.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit f7d83cbae8)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 14:08:46 +01:00
Paweł GronowskiandGitHub 5927d80c76 Merge pull request #6844 from vvoland/update-docker
vendor: github.com/moby/moby/api v1.54.0
2026-03-05 14:22:32 +00:00
Paweł Gronowski 206fc8c165 vendor: github.com/moby/moby/client v0.3.0
full diff: https://github.com/moby/moby/compare/client/v0.2.3-rc.1...client/v0.3.0

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-03-05 15:15:44 +01:00
Paweł Gronowski 874a8df0eb vendor: github.com/moby/moby/api v1.54.0
full diff: https://github.com/moby/moby/compare/api/v1.54.0-rc.1...api/v1.54.0

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-03-05 15:15:04 +01:00
Sebastiaan van StijnandGitHub 964a6d2e98 Merge pull request #6792 from vvoland/bind-create
container/opts: Add bind-create-src mount option
2026-03-05 12:39:04 +01:00
Sebastiaan van StijnandGitHub 210147d3f3 Merge pull request #6843 from docker/dependabot/github_actions/docker/setup-buildx-action-4
build(deps): bump docker/setup-buildx-action from 3 to 4
2026-03-05 11:40:17 +01:00
Sebastiaan van StijnandGitHub 847f547aa1 Merge pull request #6842 from docker/dependabot/github_actions/docker/login-action-4
build(deps): bump docker/login-action from 3 to 4
2026-03-05 11:39:15 +01:00
dependabot[bot]andGitHub 668b3671bd build(deps): bump docker/setup-buildx-action from 3 to 4
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-05 08:42:54 +00:00
dependabot[bot]andGitHub 30a2ace7f2 build(deps): bump docker/login-action from 3 to 4
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-05 08:42:47 +00:00
Nicolas De LoofandPaweł Gronowski 32aa575aff docs/service: Document bind-create-src
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-03-04 18:00:48 +01:00
Paweł Gronowski c747cff9ab container/opts: Add bind-create-src mount option
Add support for the `bind-create-src` option in bind mounts, which
instructs the daemon to create the source mountpoint on the host if it
doesn't exist.

This allows to replace the legacy `-v /src/dir:/dst` with the `--mount`.

Usage:
--mount type=bind,src=/host/path,dst=/container/path,bind-create-src
--mount type=bind,src=/host/path,dst=/container/path,bind-create-src=true

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-03-04 18:00:01 +01:00
Sebastiaan van StijnandGitHub ddb986472a Merge pull request #6794 from derekmisler/cli-hints-for-docker-ai-after-buildcompose-failur
Fix: run plugin hooks on command failure, not just success
2026-03-04 15:14:57 +01:00
Sebastiaan van StijnandGitHub 5348cf8461 Merge pull request #6836 from docker/dependabot/github_actions/docker/setup-qemu-action-4
build(deps): bump docker/setup-qemu-action from 3 to 4
2026-03-04 14:38:24 +01:00
Sebastiaan van StijnandGitHub 30fb480896 Merge pull request #6784 from thaJeztah/login_cleanups
cli/command/registry: preserve all whitespace in secrets
2026-03-04 12:53:19 +01:00
Sebastiaan van StijnandGitHub 6347345783 Merge pull request #6809 from thaJeztah/compose_rm_utils
cli/compose/loader: remove some wrapper utilities and use errors.Join
2026-03-04 12:52:36 +01:00
dependabot[bot]andGitHub ba349f5afd build(deps): bump docker/setup-qemu-action from 3 to 4
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-04 08:42:48 +00:00
Sebastiaan van StijnandGitHub 0eda6db75b Merge pull request #6833 from docker/dependabot/go_modules/cmd/docker-trust/go_modules-6b971a9d7e
build(deps): bump go.opentelemetry.io/otel/sdk from 1.38.0 to 1.40.0 in /cmd/docker-trust in the go_modules group across 1 directory
2026-03-02 15:28:07 +01:00
Sebastiaan van Stijn e2cafd657e cli/command/registry: preserve all whitespace in secrets
Preserve all whitespace and treat the secret as an opaque value,
leaving it to the registry to (in)validate. We still check for
empty values in some places.

This partially reverts a21a5f4243,
but checks for empty (whitespace-only) passwords without mutating
the value.

This better aligns with [NIST SP 800-63B §5.1.1.2], which describes
that the value should be treated as opaque, preserving any other whitespace,
including newlines. Note that trimming whitespace may still happen elsewhere
(see [NIST SP 800-63B (revision 4) §3.1.1.2]);
> Verifiers **MAY** make limited allowances for mistyping (e.g., removing
> leading and trailing whitespace characters before verification, allowing
> the verification of passwords with differing cases for the leading character)

[NIST SP 800-63B §5.1.1.2]: https://pages.nist.gov/800-63-3/sp800-63b.html#memsecretver
[NIST SP 800-63B (revision 4) §3.1.1.2]: https://pages.nist.gov/800-63-4/sp800-63b.html#passwordver

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-02 15:13:16 +01:00
dependabot[bot]andGitHub 10ebb3b204 build(deps): bump go.opentelemetry.io/otel/sdk
Bumps the go_modules group with 1 update in the /cmd/docker-trust directory: [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go).


Updates `go.opentelemetry.io/otel/sdk` from 1.38.0 to 1.40.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.38.0...v1.40.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/sdk
  dependency-version: 1.40.0
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-01 22:11:09 +00:00
Sebastiaan van StijnandGitHub 694d1a034f Merge pull request #6815 from luojiyin1987/fix-typos
Fix various typos in code and documentation
2026-02-28 20:04:24 +01:00
luojiyin 681f15674c Fix typos in code and documentation
Signed-off-by: luojiyin <luojiyin@hotmail.com>
2026-02-28 08:37:04 +08:00
Paweł GronowskiandGitHub 4e5bc6816d Merge pull request #6832 from vvoland/update-docker
vendor: github.com/moby/moby/api v1.54.0-rc.1 & client v0.2.3-rc.1
2026-02-27 19:43:43 +00:00
Paweł Gronowski 0bf060f777 vendor: github.com/moby/moby/client v0.2.3-rc.1
full diff: https://github.com/moby/moby/client/compare/52dc67c0df94...v0.2.3-rc.1

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-02-27 20:34:37 +01:00
Paweł Gronowski 139b58d7f4 vendor: github.com/moby/moby/api v1.54.0-rc.1
full diff: https://github.com/moby/moby/api/compare/52dc67c0df94...v1.54.0-rc.1

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-02-27 20:34:19 +01:00
Paweł GronowskiandGitHub 851b5b7f78 Merge pull request #6829 from thaJeztah/bump_modules
vendor: moby/api v1.54.0-dev, moby/client v0.2.3-dev
2026-02-27 18:00:07 +00:00
Sebastiaan van Stijn 8eedbdc6a8 vendor: moby/api v1.54.0-dev, moby/client v0.2.3-dev
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-27 18:16:02 +01:00
Paweł GronowskiandGitHub afb3212d5b Merge pull request #6827 from thaJeztah/bump_compose
Dockerfile: update compose to v5.1.0, buildx to v0.31.1, mvdan.cc/gofump to v0.9.2
2026-02-27 16:45:45 +00:00
Paweł GronowskiandGitHub 575793f52f Merge pull request #6828 from thaJeztah/bump_runewidth
vendor: github.com/mattn/go-runewidth v0.0.20
2026-02-27 16:45:32 +00:00
Sebastiaan van Stijn cd070a5ed5 vendor: github.com/mattn/go-runewidth v0.0.20
full diff: https://github.com/mattn/go-runewidth/compare/v0.0.19...v0.0.20

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-27 17:28:18 +01:00
Sebastiaan van Stijn 8c3d05398e Dockerfile: update mvdan.cc/gofump to v0.9.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-27 17:21:04 +01:00
Sebastiaan van Stijn b206927e0c Dockerfile: update buildx to v0.31.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-27 17:20:06 +01:00
Sebastiaan van Stijn 6b5acd3a6e Dockerfile: update compose to v5.1.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-27 17:18:48 +01:00
Paweł GronowskiandGitHub 9e10dc3dca Merge pull request #6824 from thaJeztah/bump_compress2
vendor: github.com/klauspost/compress v1.18.4
2026-02-27 15:39:27 +00:00
Sebastiaan van StijnandGitHub f8a2176b84 Merge pull request #6823 from thaJeztah/bump_otel
vendor: go.opentelemetry.io/otel v1.40.0, go.opentelemetry.io/contrib v0.65.0
2026-02-27 16:33:52 +01:00
Sebastiaan van StijnandGitHub 3169956851 Merge pull request #6822 from docker/dependabot/github_actions/actions/upload-artifact-7
build(deps): bump actions/upload-artifact from 6 to 7
2026-02-27 15:26:48 +01:00
Sebastiaan van Stijn caa8a50468 vendor: github.com/klauspost/compress v1.18.4
full diff: https://github.com/klauspost/compress/compare/v1.18.3...v1.18.4

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-27 15:23:55 +01:00
Sebastiaan van Stijn 5c498778ec vendor: go.opentelemetry.io/contrib v0.65.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-27 15:20:49 +01:00
Sebastiaan van Stijn fbd0e7f7c4 vendor: go.opentelemetry.io/otel v1.40.0
Includes fixes for [GHSA-9h8m-3fm2-qjrq] / [CVE-2026-24051] on macOS

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-27 15:16:34 +01:00
dependabot[bot]andGitHub 95a5a9e709 build(deps): bump actions/upload-artifact from 6 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-27 08:42:37 +00:00
Sebastiaan van StijnandGitHub 419e5d136c Merge pull request #6813 from dvdksn/fix-link-to-redirect
chore: use canonical url for buildx build cli doc
2026-02-23 17:58:13 +01:00
David Karlsson a7b95f228f chore: use canonical url for buildx build cli doc
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2026-02-23 17:39:19 +01:00
Sebastiaan van Stijn 13c993f101 cli/compose/loader: merge: use errors.Join
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-20 00:10:22 +01:00
Sebastiaan van Stijn 8b6f23d18b cli/compose/loader: remove some wrapper utilities
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-20 00:09:23 +01:00
Sebastiaan van StijnandGitHub d62b88939b Merge pull request #6803 from thaJeztah/compose_fixes
cli/compose: assorted fixes and cleanups
2026-02-19 23:00:44 +01:00
Sebastiaan van Stijn b35a2d0837 cli/compose/loader: remove getLoggingDriver
Inline it in mergeLoggingConfig and add some vars, which also
makes it more readable.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-19 22:48:39 +01:00
Sebastiaan van Stijn 42a211162c cli/compose/loader: mergeServices: inline mapByName
It's now only used once; let's inline it to remove some abstraction.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-19 22:48:38 +01:00
Sebastiaan van Stijn 6e1393089b cli/compose/loader: mergeServices: remove intermediate map for overrides
The code was using an intermediate map, indexed by name, for services
per file. Service-names should be unique per-file, so using an intermediate
map would not benefit us (we'd still have to loop over all of them to
produce the map, and again to iterate over the map)

Remove the intermediate map for overrides, and apply all overrides for
a service instead.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-19 22:45:41 +01:00
Sebastiaan van Stijn 78458e11e1 cli/compose/loader: mergeServices: tidy up and modernize
- construct merge-opts as a slice
- remove intermediate var for overrideServices
- use slices.SortFunc for sorting

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-19 22:45:08 +01:00
Sebastiaan van Stijn 09cf89e82e cli/compose/convert: convertEndpointSpec: fix sorting of ports
The existing code only sorted by PublishedPort (host port), and did
not account for multiple ports mapped to the same host-port, but
using a different protocol.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-19 22:45:08 +01:00
Sebastiaan van Stijn db28780976 cli/compose/convert: convertUlimits: modernize
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-19 22:45:05 +01:00
Derek Misler 830d05d16e error-hooks approach
Signed-off-by: Derek Misler <derek.misler@docker.com>
2026-02-19 10:07:57 -05:00
Derek Misler 9bc18993a4 Fix: run plugin hooks on command failure, not just success
Signed-off-by: Derek Misler <derek.misler@docker.com>
2026-02-19 10:07:57 -05:00
Sebastiaan van StijnandGitHub 44ca067062 Merge pull request #6807 from thaJeztah/less_streamformatter
remove redundant uses of streamformatter in tests
2026-02-19 14:32:57 +01:00
Sebastiaan van Stijn fdebf0afae cli/command/container: fix some unhandled errors in test
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-19 09:42:01 +01:00
Sebastiaan van Stijn e47a5c7734 remove redundant uses of streamformatter in tests
The output of this was not used in the tests, and shouldn't be
needed as part of it.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-19 09:41:17 +01:00
Sebastiaan van StijnandGitHub 63158f7ede Merge pull request #6801 from thaJeztah/no_no_netgo
scripts/build/.variables: don't use "netgo" when building Windows binaries
2026-02-16 20:52:31 +01:00
Sebastiaan van Stijn 2fa6b736d0 scripts/build/.variables: don't use "netgo" when building Windows binaries
commit 880ef756b7 fixed static builds with
CGO, which included setting the `netgo` build-tag for static builds.

Starting with go1.19, the Go runtime on Windows now supports the `netgo` build-
flag to use a native Go DNS resolver. Prior to that version, the build-flag
only had an effect on non-Windows platforms. From the go1.19 release notes:
https://go.dev/doc/go1.19#net

> Resolver.PreferGo is now implemented on Windows and Plan 9. It previously
> only worked on Unix platforms. Combined with Dialer.Resolver and Resolver.Dial,
> it's now possible to write portable programs and be in control of all DNS name
> lookups when dialing.
>
> The net package now has initial support for the netgo build tag on Windows.
> When used, the package uses the Go DNS client (as used by Resolver.PreferGo)
> instead of asking Windows for DNS results. The upstream DNS server it discovers
> from Windows may not yet be correct with complex system network configurations,
> however.

This originally caused issues in the daemon, because the pure-go implementation
did not respect file-based resolution (`C:\Windows\System32\Drivers\etc\hosts`),
resulting in `localhost` not being resolvable, and custom entries in `.etc/hosts`
not being used.

That specific problem was resolved in go1.22 (through [golang/go@33d4a51]), but
other limitations may still apply, and resolver ordering may not respect VPN
adaptors (such as Twingate) and queries sent through the local network adapter
instead of the VPN tunnel, resulting in DNS resolution failures;

    Get "https://example.com:2376/v1.52/containers/json": dial tcp: lookup example.com: i/o timeout

This patch unsets the `netgo` option when (cross-)compiling for Windows, similar
to the patch used for the daemon (see [moby/moby@53d1b12]).

[golang/go@33d4a51]: https://github.com/golang/go/commit/33d4a5105cf2b2d549922e909e9239a48b8cefcc
[moby/moby@53d1b12]: https://github.com/moby/moby/commit/53d1b12bc014b4243e9439fc2610eb4ef863659f

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-14 14:42:55 +01:00
Sebastiaan van StijnandGitHub 7b93d61673 Merge pull request #6790 from thaJeztah/bump_x_deps
vendor: update golang.org/x/* dependencies
2026-02-13 13:30:07 +01:00
Sebastiaan van StijnandGitHub fbb9cb3d73 Merge pull request #6797 from thaJeztah/login_stdin_refactor
cli/command/registry: refactor reading from stdin
2026-02-13 13:28:57 +01:00
Paweł GronowskiandGitHub 49eae5c613 Merge pull request #6798 from vvoland/issues-sbxs
github/issues: Add links for Docker Desktop and Sandboxes
2026-02-12 15:46:14 +00:00
Sebastiaan van Stijn 61f03db682 cli/command/registry: refactor reading from stdin
Extract the code as a utility function, and add some GoDoc to
describe the behavior.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-12 14:43:22 +01:00
Paweł Gronowski 74d4554ccd github/issues: Add emojis
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-02-12 14:38:16 +01:00
Paweł Gronowski 2ebd137abc github/issues: Add links for Docker Desktop and Sandboxes
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-02-12 14:38:07 +01:00
Sebastiaan van StijnandGitHub 63ba295ba4 Merge pull request #6788 from thaJeztah/bump_alpine
Dockerfile: update alpine to 3.23
2026-02-12 14:07:57 +01:00
Sebastiaan van StijnandGitHub 2d194dcf5a Merge pull request #6789 from thaJeztah/bump_golangci_lint
Dockerfile: update golangci-lint to v2.9.0 and fix linting
2026-02-12 14:07:29 +01:00
Sebastiaan van StijnandGitHub 9a41c733c4 Merge pull request #6796 from thaJeztah/login_cleanup_tests
cli/command/registry: remove uses of "gotest.tools/v3/fs"
2026-02-12 14:04:16 +01:00
Sebastiaan van Stijn f5b6055bd1 cli/command/registry: add unit test for --password-stdin
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-12 12:51:54 +01:00
Sebastiaan van Stijn b82e30e58d cli/command/registry: remove uses of "gotest.tools/v3/fs"
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-12 12:37:02 +01:00
Sebastiaan van StijnandGitHub e30ce84dfe Merge pull request #6793 from thaJeztah/update_e2e
e2e: use docker v29.x dind as default
2026-02-11 17:17:23 +01:00
Sebastiaan van StijnandGitHub 3d62a7c806 Merge pull request #6791 from thaJeztah/modernize
modernize: various cleanups
2026-02-11 16:41:36 +01:00
Sebastiaan van Stijn c4fd2406e0 e2e: use docker v29.x dind as default
Also remove groupadd which looks to be redundant now.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 15:52:01 +01:00
Sebastiaan van Stijn fddfe63ef9 modernize: fmtappendf
go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest
    modernize -fmtappendf -fix ./...

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 13:38:38 +01:00
Sebastiaan van Stijn 6d4b3b5f66 modernize: slicescontains
go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest
    modernize -slicescontains -fix ./...

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 13:35:26 +01:00
Sebastiaan van Stijn 835d510b78 modernize: stringsseq
go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest
    modernize -stringsseq -fix ./...

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 13:31:11 +01:00
Sebastiaan van Stijn dd73e2df77 modernize: reflecttypefor
go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest
    modernize -reflecttypefor -fix ./...

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 13:19:10 +01:00
Sebastiaan van Stijn 7f5bb1e99c modernize: testingcontext
go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest
    modernize -testingcontext -fix ./...

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 13:17:36 +01:00
Sebastiaan van Stijn 2875e48024 modernize: stringscut
go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest
    modernize -stringscut -fix ./...

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 13:16:13 +01:00
Sebastiaan van Stijn 4c7d40cf77 modernize: mapsloop
go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest
    modernize -mapsloop -fix ./...

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 13:14:37 +01:00
Sebastiaan van Stijn 85ebca52fd modernize: minmax
go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest
    modernize -minmax -fix ./...

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 13:08:46 +01:00
Sebastiaan van Stijn e8dc2fce32 modernize: rangeint
go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest
    modernize -rangeint -fix ./...

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 13:07:01 +01:00
Sebastiaan van Stijn e4f6019e62 vendor: golang.org/x/net v0.50.0
full diff: https://cs.opensource.google/go/x/net/+/refs/tags/v0.49.0...refs/tags/v0.50.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 12:38:03 +01:00
Sebastiaan van Stijn 464e14c68e vendor: golang.org/x/term v0.40.0
full diff: https://cs.opensource.google/go/x/term/+/refs/tags/v0.39.0...refs/tags/v0.40.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 12:36:59 +01:00
Sebastiaan van Stijn 1e31c2825e vendor: golang.org/x/text v0.34.0
full diff: https://cs.opensource.google/go/x/text/+/refs/tags/v0.33.0...refs/tags/v0.34.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 12:36:12 +01:00
Sebastiaan van Stijn 355f7bb602 vendor: golang.org/x/sys v0.41.0
full diff: https://cs.opensource.google/go/x/sys/+/refs/tags/v0.40.0...refs/tags/v0.41.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 12:35:22 +01:00
Sebastiaan van Stijn a934c75de7 Dockerfile: update golangci-lint to v2.9.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 11:30:26 +01:00
Sebastiaan van Stijn ab06aebd4b internal/volumespec: fix prealloc linting
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 11:30:26 +01:00
Sebastiaan van Stijn 9a0c78fdc0 cli-plugins/manager: fix prealloc linting
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 11:30:26 +01:00
Sebastiaan van Stijn 2e544d6308 cli/command: fix prealloc linting
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 11:30:26 +01:00
Sebastiaan van Stijn 12a0b0b7b9 cli/compose: fix prealloc linting
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 11:30:26 +01:00
Sebastiaan van Stijn 99cef6f700 opts: fix prealloc linting
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 11:30:25 +01:00
Sebastiaan van Stijn 8a8a3e1309 opts/swarmopts: fix prealloc linting
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 11:30:25 +01:00
Sebastiaan van Stijn 9d2816c8a5 remove outdated "nolint" comments
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 11:30:25 +01:00
Sebastiaan van Stijn eaba9ecf18 cli/connhelper/ssh: remove outdated "nolint" comment
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-11 11:30:25 +01:00
Paweł GronowskiandGitHub 3e466c88e1 Merge pull request #6787 from thaJeztah/fix_vol_prune_example
docs: fix docker volume prune example
2026-02-11 10:24:13 +00:00
Sebastiaan van Stijn 1f6b319d60 Dockerfile: update alpine to 3.23
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-10 20:41:08 +01:00
Sebastiaan van Stijn b598f8f0b8 docs: fix docker volume prune example
It doesn't remove named volumes by default.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-10 19:11:12 +01:00
Sebastiaan van StijnandGitHub df016a3a95 Merge pull request #6785 from thaJeztah/docs_fixes
docs: fix typos
2026-02-09 19:17:31 +01:00
Sebastiaan van Stijn 5eb91665d1 docs: fix typos
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-08 18:10:21 +01:00
Sebastiaan van StijnandGitHub 727bc3ea5b Merge pull request #6776 from whyvineet/fix-typo
docs: fix typo in dockerd.md for 'replacement'
2026-02-05 14:03:50 +01:00
Sebastiaan van StijnandGitHub b0e1e0995d Merge pull request #6781 from thaJeztah/bake_use_dockerfile_defaults
docker-bake.hcl: use default GO_VERSION from Dockerfile
2026-02-05 13:47:09 +01:00
Sebastiaan van Stijn d6c6bbf574 docker-bake.hcl: use default GO_VERSION from Dockerfile
Use the defaults as specified in the Dockerfile, unless set;
https://docs.docker.com/build/bake/reference/#variable

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-05 13:22:56 +01:00
Sebastiaan van StijnandGitHub 7de3e08796 Merge pull request #6780 from thaJeztah/bump_go1.25.7
update to go1.25.7
2026-02-05 13:08:23 +01:00
Sebastiaan van Stijn 2d5d0842c5 update to go1.25.7
go1.25.7 (released 2026-02-04) includes security fixes to the go command
and the crypto/tls package, as well as bug fixes to the compiler and the
crypto/x509 package. See the Go 1.25.7 milestone on our issue tracker for
details:
https://github.com/golang/go/issues?q=milestone%3AGo1.25.7+label%3ACherryPickApproved

full diff: https://github.com/golang/go/compare/go1.25.6...go1.25.7

From the security mailing list:

> Hello gophers,
>
> We have just released Go versions 1.25.7 and 1.24.13, minor point releases.
>
> These releases include 2 security fixes following the security policy:
>
> - cmd/cgo: remove user-content from doc strings in cgo ASTs
>
>   A discrepancy between how Go and C/C++ comments
>   were parsed allowed for code smuggling into the
>   resulting cgo binary.
>
>   To prevent this behavior, the cgo compiler
>   will no longer parse user-provided doc
>   comments.
>
>   Thank you to RyotaK (https://ryotak.net) of
>   GMO Flatt Security Inc. for reporting this issue.
>
>   This is CVE-2025-61732 and https://go.dev/issue/76697.
>
> - crypto/tls: unexpected session resumption when using Config.GetConfigForClient
>
>   Config.GetConfigForClient is documented to use the original Config's session
>   ticket keys unless explicitly overridden. This can cause unexpected behavior if
>   the returned Config modifies authentication parameters, like ClientCAs: a
>   connection initially established with the parent (or a sibling) Config can be
>   resumed, bypassing the modified authentication requirements.
>
>   If ClientAuth is VerifyClientCertIfGiven or RequireAndVerifyClientCert (on the
>   server) or InsecureSkipVerify is false (on the client), crypto/tls now checks
>   that the root of the previously-verified chain is still in ClientCAs/RootCAs
>   when resuming a connection.
>
>   Go 1.26 Release Candidate 2, Go 1.25.6, and Go 1.24.12 had fixed a similar issue
>   related to session ticket keys being implicitly shared by Config.Clone. Since
>   this fix is broader, the Config.Clone behavior change has been reverted.
>
>   Note that VerifyPeerCertificate still behaves as documented: it does not apply
>   to resumed connections. Applications that use Config.GetConfigForClient or
>   Config.Clone and do not wish to blindly resume connections established with the
>   original Config must use VerifyConnection instead (or SetSessionTicketKeys or
>   SessionTicketsDisabled).
>
>   Thanks to Coia Prant (github.com/rbqvq) for reporting this issue.
>
>   This updates CVE-2025-68121 and Go issue https://go.dev/issue/77217.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-05 12:42:11 +01:00
Vineet Kumar def41fe652 docs: fix typo in dockerd.md for 'replacement'
Signed-off-by: Vineet Kumar <vineetkumar17112004@gmail.com>
2026-02-04 23:20:34 +05:30
Sebastiaan van StijnandGitHub 769e75a0ee Merge pull request #6775 from thaJeztah/bump_xx
Dockerfile: update tonistiigi/xx to v1.9.0
2026-02-04 14:13:33 +01:00
Sebastiaan van Stijn 58413ca113 Dockerfile: update tonistiigi/xx to v1.9.0
full diff: https://github.com/tonistiigi/xx/compare/v1.7.0...v1.9.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-04 12:50:49 +01:00
Paweł GronowskiandGitHub a5c7197d72 Merge pull request #6772 from thaJeztah/cleanup_testfile
cli/command: TestGetDefaultAuthConfig: cleanup test file
2026-02-02 16:33:58 +00:00
Paweł GronowskiandGitHub 435384fa29 Merge pull request #6773 from thaJeztah/improve_mountopts
opts: MountOpt: improve validation, and refactor
2026-02-02 16:22:36 +00:00
Sebastiaan van Stijn df3e9237d7 opts: MountOpt: extract utility functions and don't set empty values
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-02 16:51:51 +01:00
Sebastiaan van Stijn d781df8b53 opts: MountOpt: extract validation to a separate function
This splits the validation code from parsing code, potentially allowing
us to either fully deferring it to the daemon, or to perform validation
separately.

For reference; daemon-side validation currently (docker 29.2.0) produces;

    docker run --rm --mount type=bind,src=/var/run,target=/foo,bind-recursive=writable alpine
    docker: Error response from daemon: mount options conflict: !ReadOnly && BindOptions.ReadOnlyNonRecursive

    docker run --rm --mount type=bind,src=/var/run,target=/foo,bind-recursive=readonly alpine
    docker: Error response from daemon: mount options conflict: !ReadOnly && BindOptions.ReadOnlyForceRecursive

Validation for BindOptions.Propagation is currently missing on the daemon;

    docker run --rm --mount type=bind,src=/var/run,target=/foo,bind-recursive=readonly,readonly alpine
    # no error

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-02 16:51:41 +01:00
Sebastiaan van Stijn f35fb0f5a6 cli/command: TestGetDefaultAuthConfig: cleanup test file
Prevent a `cli/command/filename` file being left behind after running tests.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-02 16:06:52 +01:00
Sebastiaan van Stijn fe1af9206c opts: MountOpt: improve validation of boolean values
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-02 15:29:23 +01:00
Sebastiaan van Stijn 5de99e6726 opts: MountOpt: improve validation for whitespace in values
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-02 15:29:22 +01:00
Sebastiaan van Stijn 9620e4178d opts: MountOpt: improve validation for whitespace in options
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-02 15:29:22 +01:00
Sebastiaan van Stijn e888a6e009 opts: remove outdated comment
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-02 15:29:20 +01:00
Paweł GronowskiandGitHub b22f1aef48 Merge pull request #6771 from thaJeztah/allow_empty_target
opts: MountOpt: relax client-side validation of mount target
2026-02-02 14:15:52 +00:00
Sebastiaan van Stijn bcc14559c9 opts: MountOpt: relax client-side validation of mount target
The daemon already validates the target, so we don't have to validate
if a target is set. Instead, we can ignore empty targets, but produce
an error if a target option was set, but set to an empty value.

With this patch applied, omitting a target option is ignored by the CLI,
but still invalidated by the daemon if the given mount-type requires a
mount target;

    docker run --rm --mount type=bind,src=/var/run/docker.sock alpine
    docker: Error response from daemon: invalid mount config for type "bind": field Target must not be empty

    docker run --rm --mount type=bind,src=/var/run/docker.sock,dst=../foo alpine
    docker: Error response from daemon: invalid mount config for type "bind": invalid mount path: '../foo' mount path must be absolute

When passing a target option (`target`, `dst`, or `destination`), the
CLI produces an error if the value is empty;

    docker run --rm --mount type=bind,src=/var/run/docker.sock,dst= alpine
    invalid argument "type=bind,src=/var/run/docker.sock,dst=" for "--mount" flag: invalid value for 'dst': mount target must be a non-empty value

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-02 15:05:44 +01:00
Paweł GronowskiandGitHub ffd9b407f5 Merge pull request #6770 from thaJeztah/validate_empty
opts: MountOpt: improve error for empty value
2026-02-02 13:13:28 +00:00
Sebastiaan van Stijn defbe23deb opts: MountOpt: improve error for empty value
Before this patch:

    docker run --rm --mount "" busybox
    invalid argument "" for "--mount" flag: EOF

With this patch:

    docker run --rm --mount "" busybox
    invalid argument "" for "--mount" flag: value is empty

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-02 12:28:09 +01:00
Sebastiaan van StijnandGitHub 028eee55fa Merge pull request #6768 from thaJeztah/improve_mountopts_tests
opts: improve and cleanup MountOpt tests
2026-02-02 12:14:43 +01:00
Sebastiaan van Stijn 77e02a92ec opts: MountOpt: add test-coverage for volume options
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-01 15:57:02 +01:00
Sebastiaan van Stijn 2c8bf677f0 opts: MountOpt: remove duplicate test
Setting the source and target paths is not tied to the mount-type,
so these tests where covering the same code.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-01 15:57:02 +01:00
Sebastiaan van Stijn 7ebc2f7c21 opts: MountOpt: rewrite TestMountOptVolumeNoCopy to a table-test
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-01 15:56:58 +01:00
Sebastiaan van Stijn a850b054a8 opts: MountOpt: rewrite TestMountOptDefaultEnableReadOnly to a table-test
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-01 15:56:32 +01:00
Sebastiaan van Stijn f3efc27a1a opts: MountOpt: combine error tests into a test-table
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-01 15:52:33 +01:00
Sebastiaan van StijnandGitHub 0b9d1985db Merge pull request #6764 from vvoland/update-docker
vendor: github.com/moby/moby/api v1.53.0 & github.com/moby/moby/client v0.2.2
2026-01-26 20:20:51 +01:00
Paweł Gronowski 9c9ec73588 vendor: github.com/moby/moby/client v0.2.2
full diff: https://github.com/moby/moby/client/compare/v0.2.2-rc.2...v0.2.2

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-01-26 20:15:30 +01:00
Paweł Gronowski bab3e81e1d vendor: github.com/moby/moby/api v1.53.0
full diff: https://github.com/moby/moby/api/compare/v1.53.0-rc.2...v1.53.0

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-01-26 20:15:02 +01:00
Paweł GronowskiandGitHub 2e64fc162a Merge pull request #6367 from thaJeztah/template_slicejoin
templates: make "join" work with non-string slices and map values
2026-01-26 18:03:25 +00:00
Paweł GronowskiandGitHub 1f2ba2ac9d Merge pull request #6760 from thaJeztah/container_create_fix_error
cli/command/container: make injecting config.json failures a warning
2026-01-26 17:44:42 +00:00
Sebastiaan van Stijn e34a3422cc templates: make "join" work with non-string slices and map values
Add a custom join function that allows for non-string slices to be
joined, following the same rules as "fmt.Sprint", it will use the
fmt.Stringer interface if implemented, or "error" if the type has
an "Error()".

For maps, it joins the map-values, for example:

    docker image inspect --format '{{join .Config.Labels ", "}}' ubuntu
    24.04, ubuntu

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-26 18:35:58 +01:00
Paweł GronowskiandGitHub a86356d42f Merge pull request #6763 from thaJeztah/bump_mapstructure
vendor: github.com/go-viper/mapstructure/v2 v2.5.0
2026-01-26 17:21:29 +00:00
Sebastiaan van Stijn 771660a17e vendor: github.com/go-viper/mapstructure/v2 v2.5.0
full diff: https://github.com/go-viper/mapstructure/compare/v2.4.0...v2.5.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-26 12:32:10 +01:00
Sebastiaan van StijnandGitHub 9cff36b35a Merge pull request #6762 from thaJeztah/bump_x_deps
vendor: update golang.org/x/xxx deps
2026-01-26 12:28:34 +01:00
Sebastiaan van Stijn 08ed2bc6e8 cli/command/container: make injecting config.json failures a warning
Prior to 1a502e91c9, failing to write the
container-ID to a file would return an error. After that change, we could
end up in a situation where the container was created successfully, but
we failed to inject the `config.json`. This failure would be returned as
an error, but the container was created (but no ID returned due to the error).

This patch changes the error to a warning; while not "ideal" (the container
is created, but in a "partial" state), we also shouldn't consider it to be
a hard failure; proceed as normal, to allow the user to either use the
container as-is, or to delete the container and try again.

Alternatively, we could join these errors, but the result will be ambiguous
in either case (container created, but an error occurred after the fact).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-26 12:11:35 +01:00
Sebastiaan van StijnandGitHub 0312e3d379 Merge pull request #6761 from thaJeztah/bump_compress
vendor: github.com/klauspost/compress v1.18.3
2026-01-26 12:07:38 +01:00
Sebastiaan van Stijn e9ceb2f5ad vendor: golang.org/x/net v0.49.0
full diff: https://cs.opensource.google/go/x/net/+/refs/tags/v0.48.0...refs/tags/v0.49.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-26 11:30:51 +01:00
Sebastiaan van Stijn faf8a0836e vendor: golang.org/x/term v0.39.0
full diff: https://cs.opensource.google/go/x/term/+/refs/tags/v0.38.0...refs/tags/v0.39.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-26 11:29:34 +01:00
Sebastiaan van Stijn daa4d4e4aa vendor: golang.org/x/text v0.33.0
full diff: https://cs.opensource.google/go/x/text/+/refs/tags/v0.32.0...refs/tags/v0.33.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-26 11:28:39 +01:00
Sebastiaan van Stijn a4aee9bf54 vendor: golang.org/x/sys v0.40.0
full diff: https://cs.opensource.google/go/x/sys/+/refs/tags/v0.39.0...refs/tags/v0.40.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-26 11:27:40 +01:00
Paweł GronowskiandGitHub 9f774c370f Merge pull request #6759 from thaJeztah/container_create_cleanups
cli/command/container: create: assorted cleanups and linting fixes
2026-01-26 10:25:51 +00:00
Paweł GronowskiandGitHub d0838292e1 Merge pull request #6758 from thaJeztah/cidfile_error
cli/command/container: improve CID-file errors
2026-01-26 10:25:27 +00:00
Sebastiaan van Stijn ce489e0dbb vendor: github.com/klauspost/compress v1.18.3
no changes in vendored code

- fixes / downstream CVE-2025-61728

full diff: https://github.com/klauspost/compress/compare/v1.18.2...v1.18.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-26 11:22:31 +01:00
Paweł GronowskiandGitHub 931f7a1f22 Merge pull request #6753 from thaJeztah/minor_nits
cli/command/containerd: parseSecurityOpts: remove redundant sprintf
2026-01-26 10:06:15 +00:00
Sebastiaan van Stijn ef08475961 cli/command/container: ignore "not found" error on cidfile.Close
Ignore errors when trying to remove a CID-file that no longer exists;
also remove the path from the custom error as os.Remove already returns
a os.PathError, which includes the path.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-26 09:24:08 +01:00
Sebastiaan van Stijn 16bbf5d07f cli/command/container: cidFile.Write: include CID in error message
Include the container-ID in the error message when failing to write
the ID to a file, so that the user can still find the ID of the container
that was created.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-26 09:02:32 +01:00
Sebastiaan van Stijn ed566e723f cli/command/container: createContainer: remove intermediate vars
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-24 14:33:06 +01:00
Sebastiaan van Stijn cfb71de7db cli/command/container: createContainer: remove redundant closure
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-24 14:17:05 +01:00
Sebastiaan van Stijn adfb40ceb1 cli/command/container: remove outdated TODO
This was addressed in 7bdb4df07d

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-24 14:16:06 +01:00
Sebastiaan van Stijn ceea57b46d cli/command/container: copyDockerConfigIntoContainer: close TarWriter
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-24 14:16:05 +01:00
Sebastiaan van Stijn effdf1b452 cli/command/container: rename vars to use correct camelCase
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-24 14:15:57 +01:00
Sebastiaan van Stijn ccbe206a8c cli/command/containerd: parseSecurityOpts: remove redundant sprintf
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-22 10:26:54 +01:00
Paweł GronowskiandGitHub d5ed037320 Merge pull request #6736 from thaJeztah/bump_modules
vendor: moby/api v1.53.0-rc.2, moby/client v0.2.2-rc.2
2026-01-19 12:00:25 +00:00
Sebastiaan van Stijn c8841ac1b2 vendor: moby/api v1.53.0-rc.2, moby/client v0.2.2-rc.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-19 12:55:39 +01:00
Paweł GronowskiandGitHub 714f5bfae4 Merge pull request #6744 from thaJeztah/registryclient_cleanups
internal/registryclient: minor cleanups
2026-01-16 11:33:23 +00:00
Paweł GronowskiandGitHub 49b7be0146 Merge pull request #6745 from thaJeztah/cleanup_RetrieveAuthTokenFromImage
cli/command: RetrieveAuthTokenFromImage: remove redundant conditions
2026-01-16 11:33:02 +00:00
Sebastiaan van StijnandGitHub 533934c063 Merge pull request #6750 from vvoland/update-go
update to go1.25.6
2026-01-16 10:00:51 +01:00
Paweł Gronowski 7e8457115b update to go1.25.6
This releases includes 6 security fixes following the security policy:

- archive/zip: denial of service when parsing arbitrary ZIP archives

    archive/zip used a super-linear file name indexing algorithm that is invoked the first time a file in an archive is opened. This can lead to a denial of service when consuming a maliciously constructed ZIP archive.

    Thanks to Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-61728 and Go issue https://go.dev/issue/77102.

- net/http: memory exhaustion in Request.ParseForm

    When parsing a URL-encoded form net/http may allocate an unexpected amount of
    memory when provided a large number of key-value pairs. This can result in a
    denial of service due to memory exhaustion.

    Thanks to jub0bs for reporting this issue.

    This is CVE-2025-61726 and Go issue https://go.dev/issue/77101.

- crypto/tls: Config.Clone copies automatically generated session ticket keys, session resumption does not account for the expiration of full certificate chain

    The Config.Clone methods allows cloning a Config which has already been passed
    to a TLS function, allowing it to be mutated and reused.

    If Config.SessionTicketKey has not been set, and Config.SetSessionTicketKeys has
    not been called, crypto/tls will generate random session ticket keys and
    automatically rotate them. Config.Clone would copy these automatically generated
    keys into the returned Config, meaning that the two Configs would share session
    ticket keys, allowing sessions created using one Config could be used to resume
    sessions with the other Config. This can allow clients to resume sessions even
    though the Config may be configured such that they should not be able to do so.

    Config.Clone no longer copies the automatically generated session ticket keys.
    Config.Clone still copies keys which are explicitly provided, either by setting
    Config.SessionTicketKey or by calling Config.SetSessionTicketKeys.

    This issue was discoverd by the Go Security team while investigating another
    issue reported by Coia Prant (github.com/rbqvq).

    Additionally, on the server side only the expiration of the leaf certificate, if
    one was provided during the initial handshake, was checked when considering if a
    session could be resumed. This allowed sessions to be resumed if an intermediate
    or root certificate in the chain had expired.

    Session resumption now takes into account of the full chain when determining if
    the session can be resumed.

    Thanks to Coia Prant (github.com/rbqvq) for reporting this issue.

    This is CVE-2025-68121 and Go issue https://go.dev/issue/77113.

- cmd/go: bypass of flag sanitization can lead to arbitrary code execution

    Usage of 'CgoPkgConfig' allowed execution of the pkg-config
    binary with flags that are not explicitly safe-listed.

    To prevent this behavior, compiler flags resulting from usage
    of 'CgoPkgConfig' are sanitized prior to invoking pkg-config.

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

    This is CVE-2025-61731 and go.dev/issue/77100.

- cmd/go: unexpected code execution when invoking toolchain

    The Go toolchain supports multiple VCS which are used retrieving modules and
    embedding build information into binaries.

    On systems with Mercurial installed (hg) downloading modules (e.g. via go get or
    go mod download) from non-standard sources (e.g. custom domains) can cause
    unexpected code execution due to how external VCS commands are constructed.

    On systems with Git installed, downloading and building modules with malicious
    version strings could allow an attacker to write to arbitrary files on the
    system the user has access to. This can only be triggered by explicitly
    providing the malicious version strings to the toolchain, and does not affect
    usage of @latest or bare module paths.

    The toolchain now uses safer VCS options to prevent misinterpretation of
    untrusted inputs. In addition, the toolchain now disallows module version
    strings prefixed with a "-" or "/" character.

    Thanks to splitline (@splitline) from DEVCORE Research Team for reporting this
    issue.

    This is CVE-2025-68119 and Go issue https://go.dev/issue/77099.

- crypto/tls: handshake messages may be processed at the incorrect encryption level

    During the TLS 1.3 handshake if multiple messages are sent in records that span
    encryption level boundaries (for instance the Client Hello and Encrypted
    Extensions messages), the subsequent messages may be processed before the
    encryption level changes. This can cause some minor information disclosure if a
    network-local attacker can inject messages during the handshake.

    Thanks to Coia Prant (github.com/rbqvq) for reporting this issue.

    This is CVE-2025-61730 and Go issue https://go.dev/issue/76443

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

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-01-16 09:46:08 +01:00
Sebastiaan van StijnandGitHub 7d2d923a5f Merge pull request #6746 from thaJeztah/bump_logrus
vendor: github.com/sirupsen/logrus v1.9.4
2026-01-15 18:07:25 +01:00
Paweł GronowskiandGitHub 560c3c8fa3 Merge pull request #6747 from vvoland/daemon-typo
docs: Fix daemon.json typo
2026-01-15 13:20:51 +00:00
Paweł Gronowski 86bd884ac7 docs: Fix daemon.json typo
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-01-15 12:50:14 +01:00
Sebastiaan van StijnandGitHub 5579335b82 Merge pull request #6295 from thaJeztah/less_jsonmessage
internal/jsonstream: TestDisplay use streamformatter
2026-01-15 12:10:19 +01:00
Sebastiaan van Stijn 16873675bd vendor: github.com/sirupsen/logrus v1.9.4
Notable changes:

- go.mod: update minimum supported go version to v1.17.
- go.mod: bump up dependencies.
- Touch-up godoc and add "doc" links.
- README: fix links, grammar, and update examples.
- Add GNU/Hurd support.
- Add WASI wasip1 support.
- Remove uses of deprecated `ioutil` package.
- CI: update actions and golangci-lint.
- CI: remove appveyor, add macOS.

full diff: https://github.com/sirupsen/logrus/compare/v1.9.3...v1.9.4

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-15 11:45:03 +01:00
Sebastiaan van Stijn b21139c30f internal/jsonstream: TestDisplay use streamformatter
Similar to 69854c4e08, but for the
internal/jsonstream package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-15 01:38:24 +01:00
Sebastiaan van Stijn d6cdb71e2b cli/command: RetrieveAuthTokenFromImage: remove redundant conditions
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-15 00:45:50 +01:00
Sebastiaan van StijnandGitHub 32b983a26e Merge pull request #6089 from thaJeztah/man_wrap
man: reformat docker-run.1.md to prevent linting warnings
2026-01-15 00:26:43 +01:00
Sebastiaan van Stijn b13b774e24 man: reformat docker-run.1.md to prevent linting warnings
Before this patch, lintian would complain about some lines being too long:

    lintian ./*.deb
    ...
    W: docker-ce-cli: groff-message troff:<standard input>:642: warning [p 8, 10.2i, div '3tbd1,1', 0.0i]: cannot break line [usr/share/man/man1/docker-run.1.gz:1]

    groff -t -man ./docker-run.1 > /dev/null
    troff:./docker-run.1:602: warning [p 9, 2.8i]: cannot adjust line
    troff:./docker-run.1:669: warning [p 10, 2.5i, div '3tbd1,1', 0.0i]: cannot break line

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-15 00:16:14 +01:00
Sebastiaan van Stijn 816f4556ce internal/registryclient: simplify notFoundError
- remove constructor
- fix mixed pointer/non-pointer receivers
- just embed the error we want to produce

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-15 00:06:14 +01:00
Sebastiaan van Stijn d61519f99c internal/registryclient: allEndpoints: pass through context
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-15 00:06:08 +01:00
Sebastiaan van StijnandGitHub b43d4d8e7f Merge pull request #6742 from thaJeztah/validate_detachkeys
improve validation of "--detach-keys" options
2026-01-14 23:56:07 +01:00
Sebastiaan van Stijn fe3157419c improve validation of "--detach-keys" options
Before this change, the detach-keys were not validated, and the code either
fell back to the default sequence, or returned an obscure error if the
invalid sequence would produce an error on the daemon;

Before this patch:

    docker run -it --rm --detach-keys=shift-a,b busybox
    unable to upgrade to tcp, received 400

With this patch:

    docker run -it --rm --detach-keys=shift-a,b busybox
    invalid detach keys (shift-a,b): Unknown character: 'shift-a'

Note that the "unable to upgrade to tcp, received 400" error is still
something to be looked into; the client currently discards error messages
coming from the daemon.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-14 16:57:38 +01:00
Sebastiaan van StijnandGitHub 8f50791fef Merge pull request #6740 from thaJeztah/add_WithAPIClientOptions
cli/command: add WithAPIClientOptions option
2026-01-14 09:46:57 +01:00
Sebastiaan van Stijn 6a93e78038 cli/command: add WithAPIClientOptions option
This option allows setting custom options to use when constructing
the API client.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-13 16:31:58 +01:00
Sebastiaan van StijnandGitHub 07d7ef19f3 Merge pull request #6738 from thaJeztah/fix_TestSetGoDebug
cli/command: make TestSetGoDebug more predictable
2026-01-13 16:27:35 +01:00
Sebastiaan van StijnandGitHub 6050e2bff9 Merge pull request #6741 from thaJeztah/fix_err_grammar
login: touch-up error for non-TTY
2026-01-13 16:27:01 +01:00
Sebastiaan van StijnandGitHub 5d22eaae4b Merge pull request #6739 from thaJeztah/client_opts
cli/command: DockerCli: store API-client options as field
2026-01-13 16:18:57 +01:00
Sebastiaan van Stijn db762956d1 login: touch-up error for non-TTY
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-13 15:44:04 +01:00
Sebastiaan van Stijn 4b0ec0d4ea cli/command: DockerCli: store API-client options as field
Use a more generic "clientOptions" field to store options to apply
when constructing the API client, instead of a dedicated field for
user-agent.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-13 14:48:17 +01:00
Sebastiaan van Stijn f9f2d822b9 cli/command: make TestSetGoDebug more predictable
Prevent the test from failing if GODEBUG is set in the current
environment.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-13 14:24:33 +01:00
Paweł GronowskiandGitHub bd1b1a1590 Merge pull request #6735 from thaJeztah/bump_creds_helper
vendor: github.com/docker/docker-credential-helpers v0.9.5
2026-01-12 15:09:58 +00:00
Sebastiaan van StijnandGitHub bea585067e Merge pull request #6602 from ahopp/improve-env-vars-description
Improve clarity of environment variables description
2026-01-12 13:01:58 +01:00
Andrew HoppandSebastiaan van Stijn 391acef40f Improve clarity of environment variables description
Changed the environment variables section description from:
"The following list of environment variables are supported by the `docker` command line:"

To:
"The following environment variables control the behavior of the `docker` command-line client:"

This makes it clearer that these variables control Docker's behavior, and uses the more precise term "command-line client" instead of "command line".

Signed-off-by: Andrew Hopp <andrew.hopp@me.com>
2026-01-12 11:20:18 +01:00
Sebastiaan van Stijn a6f8391c9f vendor: github.com/docker/docker-credential-helpers v0.9.5
no code changes; full diff:

https://github.com/docker/docker-credential-helpers/compare/v0.9.4...v0.9.5

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-08 18:05:14 +01:00
Sebastiaan van StijnandGitHub debbf2d4f9 Merge pull request #6723 from thaJeztah/bump_go_archive
vendor: github.com/moby/go-archive v0.2.0
2026-01-07 11:09:39 +01:00
Sebastiaan van StijnandGitHub ee0951f0e7 Merge pull request #6724 from thaJeztah/bump_x_deps
vendor: update golang.org/x/xxx dependencies
2026-01-07 11:08:15 +01:00
Paweł GronowskiandGitHub 31c58f18a5 Merge pull request #6725 from thaJeztah/test_denoise
gha: run unit-tests in go modules mode, to prevent traversing nested modules
2026-01-07 09:27:45 +00:00
Paweł GronowskiandGitHub 263562efa8 Merge pull request #6722 from thaJeztah/compose_reflectfor
cli/compose/loader: rewrite with reflect.TypeFor
2026-01-07 09:26:57 +00:00
Sebastiaan van Stijn 14cffdbfab gha: run unit-tests in go modules mode, to prevent traversing nested modules
`go list` expects a module to be valid, which means that dependencies must
either be vendored, or downloaded in the module cache. However, when working
in GOPATH mode, `go.mod` files are ignored, which means that `go list` will
traverse subdirectories, even if those are a separate module, and those modules
may not have their dependencies present.

In our case, we try to exclude those modules from paths to be tested, but
do so based on the _result_ of `go list`, which already produces errors before
we filter.

These errors do not impact out tests, as we don't run tests for those paths,
but do produce noise in CI, which can be confusing;

    go test -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/|/cmd/docker-trust')
    cmd/docker-trust/internal/trust/trust.go:28:2: cannot find package "github.com/theupdateframework/notary" in any of:
        /go/src/github.com/docker/cli/vendor/github.com/theupdateframework/notary (vendor tree)
        /usr/local/go/src/github.com/theupdateframework/notary (from $GOROOT)
        /go/src/github.com/theupdateframework/notary (from $GOPATH)
    cmd/docker-trust/internal/trust/trust.go:29:2: cannot find package "github.com/theupdateframework/notary/client" in any of:
        /go/src/github.com/docker/cli/vendor/github.com/theupdateframework/notary/client (vendor tree)
        /usr/local/go/src/github.com/theupdateframework/notary/client (from $GOROOT)
        /go/src/github.com/theupdateframework/notary/client (from $GOPATH)

This patch adds a symlink for `go.mod` and `go.sum`, so that listing the
packages happens in go modules mode, and doesn't traverse to other modules,
such as `cmd/docker-trust`.

- updates 06914dd0ff, which attempted to
  exclude the docker-trust plugin
- similar to cee9ea67fc, which made this
  change for the linter.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-24 12:47:59 +01:00
Sebastiaan van Stijn 0cd2c18580 vendor: golang.org/x/net v0.48.0
- trace: fix data race in RenderEvents
- http2, webdav, websocket: fix %q verb uses with wrong type
- http2: don't PING a responsive server when resetting a stream
- http2: support net/http.Transport.NewClientConn

full diff: https://github.com/golang/net/compare/v0.47.0...v0.48.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 22:55:24 +01:00
Sebastiaan van Stijn 647ab775d0 vendor: golang.org/x/term v0.38.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 22:55:15 +01:00
Sebastiaan van Stijn a1799eacdb vendor: golang.org/x/text v0.32.0
full diff: https://github.com/golang/text/compare/v0.31.0...v0.32.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 22:50:11 +01:00
Sebastiaan van Stijn a785333731 vendor: golang.org/x/sync v0.19.0
- errgroup: use consistent read for SetLimit panic

full diff: https://github.com/golang/sync/compare/v0.18.0...v0.19.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 22:49:12 +01:00
Sebastiaan van Stijn e2a368fa4b vendor: golang.org/x/sys v0.39.0
- Revert "cpu: add HPDS, LOR, PAN detection for arm64"
- unix: add IOCTL_MEI_* constants
- unix: fix definition of Statvfs_t for netbsd-arm

full diff: https://github.com/golang/sys/compare/v0.38.0...v0.39.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 22:48:28 +01:00
Sebastiaan van Stijn ab5d4d4f8c cli/compose/loader: rewrite with reflect.TypeFor
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 22:46:54 +01:00
Sebastiaan van StijnandGitHub 874b831c0e Merge pull request #6721 from thaJeztah/less_reflect
reduce some uses of reflect package
2025-12-23 18:42:00 +01:00
Sebastiaan van Stijn 3ce8f1d80c vendor: github.com/moby/go-archive v0.2.0
- remove aliases for deprecated types and functions
- chrootarchive: remove redundant "init" mitigation for CVE-2019-14271
- xattr: Fix OS matching

full diff: https://github.com/moby/go-archive/compare/v0.1.0...v0.2.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 18:11:31 +01:00
Sebastiaan van Stijn 8205124d5b cli/command/node: nodeContext: remove uses of reflect
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 17:54:52 +01:00
Sebastiaan van Stijn 40f052c7e1 cli/command/container: use reflect IsZero
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 17:17:12 +01:00
Sebastiaan van Stijn f28565d173 cli/command/service: replace reflect for gotest.tools assertion
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 17:17:05 +01:00
Sebastiaan van Stijn e715dd5076 cli/command/volume: remove uses of reflect in test
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 15:52:40 +01:00
Sebastiaan van Stijn 3811f24f47 cli/connhelper: replace reflect for gotest.tools assertion
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 15:52:39 +01:00
Sebastiaan van Stijn a89b2e19f5 cli/command/formatter: rewrite some tests with gotest.tools
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 15:52:39 +01:00
Sebastiaan van Stijn 90ae5b8136 cli/command: replace reflect for gotest.tools assertion
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 15:52:39 +01:00
Sebastiaan van StijnandGitHub d20f30c648 Merge pull request #6720 from thaJeztah/build_tags
opts/swarmopts: add missing build-tag
2025-12-23 13:15:25 +01:00
Sebastiaan van Stijn a0e303a0ed opts/swarmopts: add missing build-tag
This was introduced in 9c10a9c9ac, which added
use of the network.ParsePortRange.All method, which uses an iterator and
requires go1.23;

    opts/swarmopts/port.go:172:18: cannot range over pr.All() (value of func type iter.Seq[network.Port]): requires go1.23 or later (-lang was set to go1.16; check go.mod)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-23 12:17:17 +01:00
Sebastiaan van StijnandGitHub 0e38eec554 Merge pull request #6718 from thaJeztah/archive_rm_deprecated
remove uses of deprecated go-archive consts
2025-12-19 19:51:48 +01:00
Sebastiaan van Stijn 03dfab4013 remove uses of deprecated go-archive consts
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-19 18:39:13 +01:00
Austin VazquezandGitHub 06818193c7 Merge pull request #6717 from thaJeztah/bump_cdi
vendor: tags.cncf.io/container-device-interface v1.1.0
2025-12-19 08:49:08 -06:00
Sebastiaan van Stijn dd6d0cd801 vendor: tags.cncf.io/container-device-interface v1.1.0
no changes in vendored files

full diff: https://github.com/cncf-tags/container-device-interface/compare/v1.0.1...v1.1.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-18 22:49:32 +01:00
Sebastiaan van StijnandGitHub 60f06cb2df Merge pull request #6716 from vvoland/yamldocs-tty
Makefile/yamldocs: Don't require TTY
2025-12-18 17:48:48 +01:00
Paweł Gronowski 4743d1d894 Makefile/yamldocs: Don't require TTY
Make it work in GHA

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-18 17:38:14 +01:00
Sebastiaan van StijnandGitHub 55d80cca36 Merge pull request #6714 from thaJeztah/fix_api_versions
cli/command/service: fix API version for memory-swap, memory-swappiness
2025-12-18 13:54:21 +01:00
Sebastiaan van StijnandGitHub 7c38d6b59c Merge pull request #6713 from thaJeztah/remove_legacy_plugin_path
cli-plugins/manager: remove legacy system-wide cli-plugin path
2025-12-18 13:53:55 +01:00
Sebastiaan van StijnandGitHub f13565257e Merge pull request #6715 from vvoland/work-docs
docs/container: Fix dead docs reference
2025-12-18 13:41:02 +01:00
Paweł Gronowski 4851066797 docs/container: Update dead link
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-18 13:18:19 +01:00
Sebastiaan van Stijn 226af68141 cli/command/service: fix API version for memory-swap, memory-swappiness
These flags were added in 71828f2792, but
copy/pasted the annotation from `--limit-pids`.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-18 12:21:52 +01:00
Sebastiaan van Stijn 13759330b1 cli-plugins/manager: remove legacy system-wide cli-plugin path
commit 4d3a76d71e updated the list of directories
for discovering CLI plugins, adding `%ProgramFiles%\Docker\cli-plugins` for
system-wide plugins.

For backward compatibility, the `%PROGRAMDATA%\Docker\cli-plugins` was kept,
however, this location is no longer used, and not generally recommended for
storing non-data content (such as CLI plugin binaries). From the [ProgramData]
documentation:

> ProgramData specifies the path to the program-data folder (normally C:\ProgramData).
> Unlike the Program Files folder, this folder can be used by applications to store
> data for standard users, because it does not require elevated permissions.

It also mentions "It can’t contain any serviceable components.", effectively
meaning that these paths should not contain data that is managed (through
updates etc.), making it a poor choice for installing "system wide" CLI plugins.

This patch removes the path from the list, given that this location is no longer
used by Docker Desktop, and the CLI-plugin API is considered an internal
implementation (since 459c6082f8).

[ProgramData]: https://learn.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-shell-setup-folderlocations-programdata

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-18 10:23:41 +01:00
Sebastiaan van StijnandGitHub 93fa57bbcd Merge pull request #6711 from vvoland/update-docker
vendor: github.com/moby/moby/api v1.53.0-rc.1
2025-12-17 17:01:14 +01:00
Paweł Gronowski 302498c33c vendor: github.com/moby/moby/client v0.2.2-rc.1
full diff: https://github.com/moby/moby/client/compare/b2d84a3ef5a9...v0.2.2-rc.1

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-17 16:45:43 +01:00
Paweł Gronowski def847be9a vendor: github.com/moby/moby/api v1.53.0-rc.1
full diff: https://github.com/moby/moby/api/compare/b2d84a3ef5a9...v1.53.0-rc.1

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-17 16:45:23 +01:00
Sebastiaan van StijnandGitHub b9095d09ab Merge pull request #6710 from robmry/nri-info
Include NRI in "info" output
2025-12-16 16:33:40 +01:00
Rob Murray 2a903c52d4 Include NRI in info output
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-12-16 13:50:03 +00:00
Rob Murray d8351dbe65 Vendor moby/[api|client] from moby master
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-12-16 13:49:49 +00:00
467 changed files with 36624 additions and 56694 deletions
+9 -3
View File
@@ -1,11 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: "Contributing to Docker"
- name: "🗃️ Docker Sandboxes: report an issue"
about: "Issues with Docker Sandboxes should be filed in the Docker Desktop feedback tracker (not docker/cli)."
url: "https://github.com/docker/desktop-feedback/issues"
- name: "🖥️ Docker Desktop: report an issue"
about: "General Docker Desktop issues (installation, upgrades, UI, networking on macOS/Windows, WSL2, etc.) should be filed in the Docker Desktop feedback tracker (not docker/cli)."
url: "https://github.com/docker/desktop-feedback/issues"
- name: "🧑‍💻 Contributing to Docker"
about: "Read guidelines and tips about contributing to Docker."
url: "https://github.com/docker/cli/blob/master/CONTRIBUTING.md"
- name: "Security and Vulnerabilities"
- name: "🔒 Security and Vulnerabilities"
about: "Report any security issues or vulnerabilities responsibly to the Docker security team. Do not use the public issue tracker."
url: "https://github.com/moby/moby/security/policy"
- name: "General Support"
- name: "💬 General Support"
about: "Get the help you need to build, share, and run your Docker applications"
url: "https://www.docker.com/support/"
+2
View File
@@ -7,3 +7,5 @@ updates:
labels:
- "area/testing"
- "status/2-code-review"
cooldown:
default-days: 7
+12 -12
View File
@@ -35,7 +35,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Create matrix
id: platforms
@@ -63,10 +63,10 @@ jobs:
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Build
uses: docker/bake-action@v6
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7
with:
targets: ${{ matrix.target }}
set: |
@@ -88,7 +88,7 @@ jobs:
fi
-
name: Upload artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: ${{ env.ARTIFACT_NAME }}
path: /tmp/out/*
@@ -101,20 +101,20 @@ jobs:
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
with:
username: ${{ secrets.DOCKERHUB_CLIBIN_USERNAME }}
password: ${{ secrets.DOCKERHUB_CLIBIN_TOKEN }}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
with:
images: dockereng/cli-bin
tags: |
@@ -125,7 +125,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
-
name: Build and push image
uses: docker/bake-action@v6
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7
with:
files: |
./docker-bake.hcl
@@ -143,7 +143,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Create matrix
id: platforms
@@ -165,10 +165,10 @@ jobs:
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Build
uses: docker/bake-action@v6
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7
with:
targets: plugins-cross
set: |
+7 -6
View File
@@ -46,7 +46,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 2
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
@@ -61,19 +61,20 @@ jobs:
ln -s vendor.sum go.sum
-
name: Update Go
uses: actions/setup-go@v6
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version: "1.25.5"
go-version: "1.25.8"
cache: false
-
name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
with:
languages: go
-
name: Autobuild
uses: github/codeql-action/autobuild@v4
uses: github/codeql-action/autobuild@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
with:
category: "/language:go"
+3 -3
View File
@@ -44,7 +44,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Update daemon.json
run: |
@@ -63,7 +63,7 @@ jobs:
docker info
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Run ${{ matrix.target }}
run: |
@@ -74,7 +74,7 @@ jobs:
TESTFLAGS: -coverprofile=/tmp/coverage/coverage.txt
-
name: Send to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
with:
files: ./build/coverage/coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
+12 -8
View File
@@ -30,15 +30,15 @@ jobs:
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Test
uses: docker/bake-action@v6
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7
with:
targets: test-coverage
-
name: Send to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
with:
files: ./build/coverage/coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
@@ -60,24 +60,28 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
path: ${{ env.GOPATH }}/src/github.com/docker/cli
-
name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version: "1.25.5"
go-version: "1.25.8"
cache: false
-
name: Test
run: |
go test -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/|/cmd/docker-trust')
# run in go modules mode to prevent traversing to nested modules
ln -s vendor.mod go.mod
ln -s vendor.sum go.sum
go test -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '^github.com/docker/cli/e2e/')
go tool cover -func=/tmp/coverage.txt
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
shell: bash
-
name: Send to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
with:
files: /tmp/coverage.txt
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
+3 -3
View File
@@ -38,7 +38,7 @@ jobs:
steps:
-
name: Run
uses: docker/bake-action@v6
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7
with:
targets: ${{ matrix.target }}
@@ -48,7 +48,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Generate
shell: 'script --return --quiet --command "bash {0}"'
@@ -74,7 +74,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Run
shell: 'script --return --quiet --command "bash {0}"'
+1 -1
View File
@@ -5,7 +5,7 @@ run:
# which causes it to fallback to go1.17 semantics.
#
# TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24
go: "1.25.5"
go: "1.25.8"
timeout: 5m
+5 -5
View File
@@ -5,14 +5,14 @@ ARG BASE_VARIANT=alpine
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
# It must be a supported tag in the docker.io/library/alpine image repository
# that's also available as alpine image variant for the Golang version used.
ARG ALPINE_VERSION=3.22
ARG ALPINE_VERSION=3.23
ARG BASE_DEBIAN_DISTRO=bookworm
ARG GO_VERSION=1.25.5
ARG GO_VERSION=1.25.8
# XX_VERSION specifies the version of the xx utility to use.
# It must be a valid tag in the docker.io/tonistiigi/xx image repository.
ARG XX_VERSION=1.7.0
ARG XX_VERSION=1.9.0
# GOVERSIONINFO_VERSION is the version of GoVersionInfo to install.
# It must be a valid tag from https://github.com/josephspurrier/goversioninfo
@@ -25,12 +25,12 @@ ARG GOTESTSUM_VERSION=v1.13.0
# BUILDX_VERSION sets the version of buildx to use for the e2e tests.
# It must be a tag in the docker.io/docker/buildx-bin image repository
# on Docker Hub.
ARG BUILDX_VERSION=0.29.1
ARG BUILDX_VERSION=0.31.1
# COMPOSE_VERSION is the version of compose to install in the dev container.
# It must be a tag in the docker.io/docker/compose-bin image repository
# on Docker Hub.
ARG COMPOSE_VERSION=v2.40.0
ARG COMPOSE_VERSION=v5.1.0
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
+1 -1
View File
@@ -151,7 +151,7 @@ func TestValidateCandidate(t *testing.T) {
assert.ErrorContains(t, err, tc.err)
case tc.invalid != "":
assert.NilError(t, err)
assert.Assert(t, is.ErrorType(p.Err, reflect.TypeOf(&pluginError{})))
assert.Assert(t, is.ErrorType(p.Err, reflect.TypeFor[*pluginError]()))
assert.ErrorContains(t, p.Err, tc.invalid)
default:
assert.NilError(t, err)
+1 -1
View File
@@ -57,7 +57,7 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Delegate completion to plugin
cargs := []string{p.Path, cobra.ShellCompRequestCmd, p.Name}
cargs := []string{p.Path, cobra.ShellCompRequestCmd, p.Name} //nolint:prealloc // no need to over-complicate things.
cargs = append(cargs, args...)
cargs = append(cargs, toComplete)
os.Args = cargs
+1 -1
View File
@@ -28,7 +28,7 @@ func (e *pluginError) Unwrap() error {
return e.cause
}
// MarshalText marshalls the pluginError into a textual form.
// MarshalText marshals the pluginError into a textual form.
func (e *pluginError) MarshalText() (text []byte, err error) {
return []byte(e.cause.Error()), nil
}
+31 -8
View File
@@ -1,3 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package manager
import (
@@ -39,11 +42,11 @@ func RunCLICommandHooks(ctx context.Context, dockerCLI config.Provider, rootCmd,
// RunPluginHooks is the entrypoint for the hooks execution flow
// after a plugin command was just executed by the CLI.
func RunPluginHooks(ctx context.Context, dockerCLI config.Provider, rootCmd, subCommand *cobra.Command, args []string) {
func RunPluginHooks(ctx context.Context, dockerCLI config.Provider, rootCmd, subCommand *cobra.Command, args []string, cmdErrorMessage string) {
commandName := strings.Join(args, " ")
flags := getNaiveFlags(args)
runHooks(ctx, dockerCLI.ConfigFile(), rootCmd, subCommand, commandName, flags, "")
runHooks(ctx, dockerCLI.ConfigFile(), rootCmd, subCommand, commandName, flags, cmdErrorMessage)
}
func runHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) {
@@ -67,7 +70,7 @@ func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, root
pluginDirs := getPluginDirs(cfg)
nextSteps := make([]string, 0, len(pluginsCfg))
for pluginName, pluginCfg := range pluginsCfg {
match, ok := pluginMatch(pluginCfg, subCmdStr)
match, ok := pluginMatch(pluginCfg, subCmdStr, cmdErrorMessage)
if !ok {
continue
}
@@ -135,14 +138,34 @@ func appendNextSteps(nextSteps []string, processed []string) ([]string, bool) {
// command being executed (such as 'image ls' the root 'docker' is omitted)
// and, if the configuration includes a hook for the invoked command, returns
// the configured hook string.
func pluginMatch(pluginCfg map[string]string, subCmd string) (string, bool) {
configuredPluginHooks, ok := pluginCfg["hooks"]
if !ok || configuredPluginHooks == "" {
//
// Plugins can declare two types of hooks in their configuration:
// - "hooks": fires on every command invocation (success or failure)
// - "error-hooks": fires only when a command fails (cmdErrorMessage is non-empty)
func pluginMatch(pluginCfg map[string]string, subCmd string, cmdErrorMessage string) (string, bool) {
// Check "hooks" first — these always fire regardless of command outcome.
if match, ok := matchHookConfig(pluginCfg["hooks"], subCmd); ok {
return match, true
}
// Check "error-hooks" — these only fire when there was an error.
if cmdErrorMessage != "" {
if match, ok := matchHookConfig(pluginCfg["error-hooks"], subCmd); ok {
return match, true
}
}
return "", false
}
// matchHookConfig checks if a comma-separated hook configuration string
// contains a prefix match for the given subcommand.
func matchHookConfig(configuredHooks string, subCmd string) (string, bool) {
if configuredHooks == "" {
return "", false
}
commands := strings.Split(configuredPluginHooks, ",")
for _, hookCmd := range commands {
for hookCmd := range strings.SplitSeq(configuredHooks, ",") {
if hookMatch(hookCmd, subCmd) {
return hookCmd, true
}
+239 -7
View File
@@ -1,12 +1,23 @@
package manager
import (
"context"
"testing"
"github.com/docker/cli/cli/config/configfile"
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
type fakeConfigProvider struct {
cfg *configfile.ConfigFile
}
func (f *fakeConfigProvider) ConfigFile() *configfile.ConfigFile {
return f.cfg
}
func TestGetNaiveFlags(t *testing.T) {
testCases := []struct {
args []string
@@ -40,12 +51,15 @@ func TestGetNaiveFlags(t *testing.T) {
func TestPluginMatch(t *testing.T) {
testCases := []struct {
commandString string
pluginConfig map[string]string
expectedMatch string
expectedOk bool
doc string
commandString string
pluginConfig map[string]string
cmdErrorMessage string
expectedMatch string
expectedOk bool
}{
{
doc: "hooks prefix match",
commandString: "image ls",
pluginConfig: map[string]string{
"hooks": "image",
@@ -54,6 +68,7 @@ func TestPluginMatch(t *testing.T) {
expectedOk: true,
},
{
doc: "hooks no match",
commandString: "context ls",
pluginConfig: map[string]string{
"hooks": "build",
@@ -62,6 +77,7 @@ func TestPluginMatch(t *testing.T) {
expectedOk: false,
},
{
doc: "hooks exact match",
commandString: "context ls",
pluginConfig: map[string]string{
"hooks": "context ls",
@@ -70,6 +86,7 @@ func TestPluginMatch(t *testing.T) {
expectedOk: true,
},
{
doc: "hooks first match wins",
commandString: "image ls",
pluginConfig: map[string]string{
"hooks": "image ls,image",
@@ -78,6 +95,7 @@ func TestPluginMatch(t *testing.T) {
expectedOk: true,
},
{
doc: "hooks empty string",
commandString: "image ls",
pluginConfig: map[string]string{
"hooks": "",
@@ -86,6 +104,7 @@ func TestPluginMatch(t *testing.T) {
expectedOk: false,
},
{
doc: "hooks partial token no match",
commandString: "image inspect",
pluginConfig: map[string]string{
"hooks": "image i",
@@ -94,6 +113,7 @@ func TestPluginMatch(t *testing.T) {
expectedOk: false,
},
{
doc: "hooks prefix token match",
commandString: "image inspect",
pluginConfig: map[string]string{
"hooks": "image",
@@ -101,12 +121,140 @@ func TestPluginMatch(t *testing.T) {
expectedMatch: "image",
expectedOk: true,
},
{
doc: "error-hooks match on error",
commandString: "build",
pluginConfig: map[string]string{
"error-hooks": "build",
},
cmdErrorMessage: "exit status 1",
expectedMatch: "build",
expectedOk: true,
},
{
doc: "error-hooks no match on success",
commandString: "build",
pluginConfig: map[string]string{
"error-hooks": "build",
},
cmdErrorMessage: "",
expectedMatch: "",
expectedOk: false,
},
{
doc: "error-hooks prefix match on error",
commandString: "compose up",
pluginConfig: map[string]string{
"error-hooks": "compose",
},
cmdErrorMessage: "exit status 1",
expectedMatch: "compose",
expectedOk: true,
},
{
doc: "error-hooks no match for wrong command",
commandString: "pull",
pluginConfig: map[string]string{
"error-hooks": "build",
},
cmdErrorMessage: "exit status 1",
expectedMatch: "",
expectedOk: false,
},
{
doc: "hooks takes precedence over error-hooks",
commandString: "build",
pluginConfig: map[string]string{
"hooks": "build",
"error-hooks": "build",
},
cmdErrorMessage: "exit status 1",
expectedMatch: "build",
expectedOk: true,
},
{
doc: "hooks fires on success even with error-hooks configured",
commandString: "build",
pluginConfig: map[string]string{
"hooks": "build",
"error-hooks": "build",
},
cmdErrorMessage: "",
expectedMatch: "build",
expectedOk: true,
},
{
doc: "error-hooks with multiple commands",
commandString: "compose up",
pluginConfig: map[string]string{
"error-hooks": "build,compose up,pull",
},
cmdErrorMessage: "exit status 1",
expectedMatch: "compose up",
expectedOk: true,
},
}
for _, tc := range testCases {
match, ok := pluginMatch(tc.pluginConfig, tc.commandString)
assert.Equal(t, ok, tc.expectedOk)
assert.Equal(t, match, tc.expectedMatch)
t.Run(tc.doc, func(t *testing.T) {
match, ok := pluginMatch(tc.pluginConfig, tc.commandString, tc.cmdErrorMessage)
assert.Equal(t, ok, tc.expectedOk)
assert.Equal(t, match, tc.expectedMatch)
})
}
}
func TestMatchHookConfig(t *testing.T) {
testCases := []struct {
doc string
configuredHooks string
subCmd string
expectedMatch string
expectedOk bool
}{
{
doc: "empty config",
configuredHooks: "",
subCmd: "build",
expectedMatch: "",
expectedOk: false,
},
{
doc: "exact match",
configuredHooks: "build",
subCmd: "build",
expectedMatch: "build",
expectedOk: true,
},
{
doc: "prefix match",
configuredHooks: "image",
subCmd: "image ls",
expectedMatch: "image",
expectedOk: true,
},
{
doc: "comma-separated match",
configuredHooks: "pull,build,push",
subCmd: "build",
expectedMatch: "build",
expectedOk: true,
},
{
doc: "no match",
configuredHooks: "pull,push",
subCmd: "build",
expectedMatch: "",
expectedOk: false,
},
}
for _, tc := range testCases {
t.Run(tc.doc, func(t *testing.T) {
match, ok := matchHookConfig(tc.configuredHooks, tc.subCmd)
assert.Equal(t, ok, tc.expectedOk)
assert.Equal(t, match, tc.expectedMatch)
})
}
}
@@ -141,3 +289,87 @@ func TestAppendNextSteps(t *testing.T) {
})
}
}
func TestRunPluginHooksPassesErrorMessage(t *testing.T) {
cfg := configfile.New("")
cfg.Plugins = map[string]map[string]string{
"test-plugin": {"hooks": "build"},
}
provider := &fakeConfigProvider{cfg: cfg}
root := &cobra.Command{Use: "docker"}
sub := &cobra.Command{Use: "build"}
root.AddCommand(sub)
// Should not panic with empty error message (success case)
RunPluginHooks(context.Background(), provider, root, sub, []string{"build"}, "")
// Should not panic with non-empty error message (failure case)
RunPluginHooks(context.Background(), provider, root, sub, []string{"build"}, "exit status 1")
}
func TestRunPluginHooksErrorHooks(t *testing.T) {
cfg := configfile.New("")
cfg.Plugins = map[string]map[string]string{
"test-plugin": {"error-hooks": "build"},
}
provider := &fakeConfigProvider{cfg: cfg}
root := &cobra.Command{Use: "docker"}
sub := &cobra.Command{Use: "build"}
root.AddCommand(sub)
// Should not panic — error-hooks with error message
RunPluginHooks(context.Background(), provider, root, sub, []string{"build"}, "exit status 1")
// Should not panic — error-hooks with no error (should be skipped)
RunPluginHooks(context.Background(), provider, root, sub, []string{"build"}, "")
}
func TestInvokeAndCollectHooksErrorHooksSkippedOnSuccess(t *testing.T) {
cfg := configfile.New("")
cfg.Plugins = map[string]map[string]string{
"nonexistent": {"error-hooks": "build"},
}
root := &cobra.Command{Use: "docker"}
sub := &cobra.Command{Use: "build"}
root.AddCommand(sub)
// On success, error-hooks should not match, so the plugin
// binary is never looked up and no results are returned.
result := invokeAndCollectHooks(
context.Background(), cfg, root, sub,
"build", map[string]string{}, "",
)
assert.Check(t, is.Len(result, 0))
}
func TestInvokeAndCollectHooksNoPlugins(t *testing.T) {
cfg := configfile.New("")
root := &cobra.Command{Use: "docker"}
sub := &cobra.Command{Use: "build"}
root.AddCommand(sub)
result := invokeAndCollectHooks(
context.Background(), cfg, root, sub,
"build", map[string]string{}, "some error",
)
assert.Check(t, is.Len(result, 0))
}
func TestInvokeAndCollectHooksCancelledContext(t *testing.T) {
cfg := configfile.New("")
cfg.Plugins = map[string]map[string]string{
"test-plugin": {"hooks": "build"},
}
root := &cobra.Command{Use: "docker"}
sub := &cobra.Command{Use: "build"}
root.AddCommand(sub)
ctx, cancel := context.WithCancel(context.Background())
cancel() // cancel immediately
result := invokeAndCollectHooks(
ctx, cfg, root, sub,
"build", map[string]string{}, "exit status 1",
)
assert.Check(t, is.Nil(result))
}
+1 -1
View File
@@ -172,7 +172,7 @@ func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, e
}
// PluginRunCommand returns an [os/exec.Cmd] which when [os/exec.Cmd.Run] will execute the named plugin.
// The rootcmd argument is referenced to determine the set of builtin commands in order to detect conficts.
// The rootcmd argument is referenced to determine the set of builtin commands in order to detect conflicts.
// The error returned satisfies the [errdefs.IsNotFound] predicate if no plugin was found or if the first candidate plugin was invalid somehow.
func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Command) (*exec.Cmd, error) {
// This uses the full original args, not the args which may
+1 -1
View File
@@ -89,7 +89,7 @@ func TestListPluginCandidatesEmpty(t *testing.T) {
// Regression test for https://github.com/docker/cli/issues/5643.
// Check that inaccessible directories that come before accessible ones are ignored
// and do not prevent the latter from being processed.
func TestListPluginCandidatesInaccesibleDir(t *testing.T) {
func TestListPluginCandidatesInaccessibleDir(t *testing.T) {
dir := fs.NewDir(t, t.Name(),
fs.WithDir("no-perm", fs.WithMode(0)),
fs.WithDir("plugins",
-1
View File
@@ -16,6 +16,5 @@ import (
//
// [ConfigFile.CLIPluginsExtraDirs]: https://pkg.go.dev/github.com/docker/cli@v26.1.4+incompatible/cli/config/configfile#ConfigFile.CLIPluginsExtraDirs
var defaultSystemPluginDirs = []string{
filepath.Join(os.Getenv("ProgramData"), "Docker", "cli-plugins"),
filepath.Join(os.Getenv("ProgramFiles"), "Docker", "cli-plugins"),
}
+1 -1
View File
@@ -93,7 +93,7 @@ func (pl *PluginServer) Addr() net.Addr {
// Close ensures that the server is no longer accepting new connections and
// closes all existing connections. Existing connections will receive [io.EOF].
//
// The error value is that of the underlying [net.Listner.Close] call.
// The error value is that of the underlying [net.Listener.Close] call.
func (pl *PluginServer) Close() error {
if pl == nil {
return nil
+1 -1
View File
@@ -47,7 +47,7 @@ func TestPluginServer(t *testing.T) {
select {
case err := <-done:
if !errors.Is(err, io.EOF) {
t.Fatalf("exepcted EOF error, got: %v", err)
t.Fatalf("expected EOF error, got: %v", err)
}
case <-time.After(10 * time.Millisecond):
}
+2 -3
View File
@@ -60,6 +60,7 @@ type Cli interface {
type DockerCli struct {
configFile *configfile.ConfigFile
options *cliflags.ClientOptions
clientOpts []client.Opt
in *streams.In
out *streams.Out
err *streams.Out
@@ -72,7 +73,6 @@ type DockerCli struct {
dockerEndpoint docker.Endpoint
contextStoreConfig *store.Config
initTimeout time.Duration
userAgent string
res telemetryResource
// baseCtx is the base context used for internal operations. In the future
@@ -533,8 +533,7 @@ func (cli *DockerCli) initialize() error {
return
}
if cli.client == nil {
ops := []client.Opt{client.WithUserAgent(cli.userAgent)}
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil {
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, cli.clientOpts...); cli.initErr != nil {
return
}
}
+11 -1
View File
@@ -104,6 +104,16 @@ func WithInitializeClient(makeClient func(*DockerCli) (client.APIClient, error))
}
}
// WithAPIClientOptions configures additional [client.Opt] to use when
// initializing the API client. These options have no effect if a custom
// client is set (through [WithAPIClient] or [WithInitializeClient]).
func WithAPIClientOptions(c ...client.Opt) CLIOption {
return func(cli *DockerCli) error {
cli.clientOpts = append(cli.clientOpts, c...)
return nil
}
}
// envOverrideHTTPHeaders is the name of the environment-variable that can be
// used to set custom HTTP headers to be sent by the client. This environment
// variable is the equivalent to the HttpHeaders field in the configuration
@@ -221,7 +231,7 @@ func WithUserAgent(userAgent string) CLIOption {
if userAgent == "" {
return errors.New("user agent cannot be blank")
}
cli.userAgent = userAgent
cli.clientOpts = append(cli.clientOpts, client.WithUserAgent(userAgent))
return nil
}
}
+24
View File
@@ -358,6 +358,7 @@ func TestSetGoDebug(t *testing.T) {
assert.Equal(t, "val1,val2", os.Getenv("GODEBUG"))
})
t.Run("GODEBUG in context metadata can set env", func(t *testing.T) {
t.Setenv("GODEBUG", "")
meta := store.Metadata{
Metadata: DockerContext{
AdditionalFields: map[string]any{
@@ -392,3 +393,26 @@ func TestNewDockerCliWithCustomUserAgent(t *testing.T) {
assert.NilError(t, err)
assert.DeepEqual(t, received, "fake-agent/0.0.1")
}
func TestNewDockerCliWithAPIClientOptions(t *testing.T) {
var received string
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
received = r.UserAgent()
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()
host := strings.Replace(ts.URL, "http://", "tcp://", 1)
opts := &flags.ClientOptions{Hosts: []string{host}}
cli, err := NewDockerCli(
WithAPIClientOptions(client.WithUserAgent("fake-agent/0.0.1")),
)
assert.NilError(t, err)
cli.currentContext = DefaultContextName
cli.options = opts
cli.configFile = &configfile.ConfigFile{}
_, err = cli.Client().Ping(t.Context(), client.PingOptions{})
assert.NilError(t, err)
assert.DeepEqual(t, received, "fake-agent/0.0.1")
}
+8 -5
View File
@@ -70,6 +70,14 @@ func newAttachCommand(dockerCLI command.Cli) *cobra.Command {
// RunAttach executes an `attach` command
func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, opts *AttachOptions) error {
detachKeys := opts.DetachKeys
if detachKeys == "" {
detachKeys = dockerCLI.ConfigFile().DetachKeys
}
if err := validateDetachKeys(detachKeys); err != nil {
return err
}
apiClient := dockerCLI.Client()
// request channel to wait for client
@@ -85,11 +93,6 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
return err
}
detachKeys := dockerCLI.ConfigFile().DetachKeys
if opts.DetachKeys != "" {
detachKeys = opts.DetachKeys
}
options := client.ContainerAttachOptions{
Stream: true,
Stdin: !opts.NoStdin && c.Config.OpenStdin,
+8
View File
@@ -27,6 +27,14 @@ func TestNewAttachCommandErrors(t *testing.T) {
return client.ContainerInspectResult{}, errors.New("something went wrong")
},
},
{
name: "invalid-detach-keys",
args: []string{"--detach-keys", "shift-b", "5cb5bb5e4a3b"},
expectedError: "invalid detach keys (shift-b):",
containerInspectFunc: func(containerID string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{}, errors.New("something went wrong")
},
},
{
name: "client-stopped",
args: []string{"5cb5bb5e4a3b"},
+30 -41
View File
@@ -132,7 +132,6 @@ func runCreate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet,
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)
if err != nil {
@@ -177,8 +176,8 @@ func (cid *cidFile) Close() error {
if cid.written {
return nil
}
if err := os.Remove(cid.path); err != nil {
return fmt.Errorf("failed to remove the CID file '%s': %w", cid.path, err)
if err := os.Remove(cid.path); err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to remove the CID file: %w", err)
}
return nil
@@ -188,8 +187,8 @@ func (cid *cidFile) Write(id string) error {
if cid.file == nil {
return nil
}
if _, err := cid.file.Write([]byte(id)); err != nil {
return fmt.Errorf("failed to write the container ID to the file: %w", err)
if _, err := cid.file.WriteString(id); err != nil {
return fmt.Errorf("failed to write the container ID (%s) to file: %w", id, err)
}
cid.written = true
return nil
@@ -212,7 +211,7 @@ func newCIDFile(cidPath 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, options *createOptions) (containerID string, _ error) {
config := containerCfg.Config
hostConfig := containerCfg.HostConfig
networkingConfig := containerCfg.NetworkingConfig
@@ -221,8 +220,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
// TODO(thaJeztah): add a platform option-type / flag-type.
if options.platform != "" {
_, err = platforms.Parse(options.platform)
if err != nil {
if _, err := platforms.Parse(options.platform); err != nil {
return "", err
}
}
@@ -248,10 +246,11 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
if options.useAPISocket {
// We'll create two new mounts to handle this flag:
//
// 1. Mount the actual docker socket.
// 2. A synthezised ~/.docker/config.json with resolved tokens.
// 2. A synthesized ~/.docker/config.json with resolved tokens.
if dockerCli.ServerInfo().OSType == "windows" {
if dockerCLI.ServerInfo().OSType == "windows" {
return "", errors.New("flag --use-api-socket can't be used with a Windows Docker Engine")
}
@@ -286,18 +285,18 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
})
*/
var envvarPresent bool
for _, envvar := range containerCfg.Config.Env {
if strings.HasPrefix(envvar, "DOCKER_CONFIG=") {
envvarPresent = true
var envVarPresent bool
for _, envVar := range containerCfg.Config.Env {
if strings.HasPrefix(envVar, "DOCKER_CONFIG=") {
envVarPresent = true
}
}
// If the DOCKER_CONFIG env var is already present, we assume the client knows
// what they're doing and don't inject the creds.
if !envvarPresent {
if !envVarPresent {
// Resolve this here for later, ensuring we error our before we create the container.
creds, err := readCredentials(dockerCli)
creds, err := readCredentials(dockerCLI)
if err != nil {
return "", fmt.Errorf("resolving credentials failed: %w", err)
}
@@ -319,22 +318,15 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
platform = &p
}
pullAndTagImage := func() error {
if err := pullImage(ctx, dockerCli, config.Image, options); err != nil {
return err
}
return nil
}
if options.pull == PullImageAlways {
if err := pullAndTagImage(); err != nil {
if err := pullImage(ctx, dockerCLI, config.Image, options); err != nil {
return "", err
}
}
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCLI.Out().GetTtySize()
response, err := dockerCli.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
response, err := dockerCLI.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
Name: options.name,
// Image: config.Image, // TODO(thaJeztah): pass image-ref separate
Platform: platform,
@@ -347,15 +339,15 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
if errdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing {
if !options.quiet {
// we don't want to write to stdout anything apart from container.ID
_, _ = fmt.Fprintf(dockerCli.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
_, _ = fmt.Fprintf(dockerCLI.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
}
if err := pullAndTagImage(); err != nil {
if err := pullImage(ctx, dockerCLI, config.Image, options); err != nil {
return "", err
}
var retryErr error
response, retryErr = dockerCli.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
response, retryErr = dockerCLI.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
Name: options.name,
// Image: config.Image, // TODO(thaJeztah): pass image-ref separate
Platform: platform,
@@ -371,24 +363,20 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
}
}
containerID = response.ID
for _, w := range response.Warnings {
_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
}
err = containerIDFile.Write(containerID)
if options.useAPISocket && len(apiSocketCreds) > 0 {
// Create a new config file with just the auth.
newConfig := &configfile.ConfigFile{
if err := copyDockerConfigIntoContainer(ctx, dockerCLI.Client(), response.ID, dockerConfigPathInContainer, &configfile.ConfigFile{
AuthConfigs: apiSocketCreds,
}
if err := copyDockerConfigIntoContainer(ctx, dockerCli.Client(), containerID, dockerConfigPathInContainer, newConfig); err != nil {
return "", fmt.Errorf("injecting docker config.json into container failed: %w", err)
}); err != nil {
response.Warnings = append(response.Warnings, fmt.Sprintf("injecting docker config.json into container failed: %v", err))
}
}
for _, w := range response.Warnings {
_, _ = fmt.Fprintln(dockerCLI.Err(), "WARNING:", w)
}
return containerID, err
err = containerIDFile.Write(response.ID)
return response.ID, err
}
func validatePullOpt(val string) error {
@@ -428,6 +416,7 @@ func copyDockerConfigIntoContainer(ctx context.Context, apiClient client.APIClie
})
if _, err := io.Copy(tarWriter, &configBuf); err != nil {
_ = tarWriter.Close()
return fmt.Errorf("writing config to tar file for config copy: %w", err)
}
+24 -9
View File
@@ -5,6 +5,7 @@ import (
"errors"
"io"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
@@ -42,17 +43,31 @@ func TestNewCIDFileWhenFileAlreadyExists(t *testing.T) {
}
func TestCIDFileCloseWithNoWrite(t *testing.T) {
tempdir := fs.NewDir(t, "test-cid-file")
defer tempdir.Remove()
// Closing should remove the file if it was not written to.
t.Run("closing should remove file", func(t *testing.T) {
filename := filepath.Join(t.TempDir(), "cidfile-1")
file, err := newCIDFile(filename)
assert.NilError(t, err)
assert.Check(t, is.Equal(file.path, filename))
path := tempdir.Join("cidfile")
file, err := newCIDFile(path)
assert.NilError(t, err)
assert.Check(t, is.Equal(file.path, path))
assert.NilError(t, file.Close())
_, err = os.Stat(filename)
assert.Check(t, os.IsNotExist(err))
})
assert.NilError(t, file.Close())
_, err = os.Stat(path)
assert.Check(t, os.IsNotExist(err))
// Closing (and removing) the file should not produce an error if the file no longer exists.
t.Run("close should remove file", func(t *testing.T) {
filename := filepath.Join(t.TempDir(), "cidfile-2")
file, err := newCIDFile(filename)
assert.NilError(t, err)
assert.Check(t, is.Equal(file.path, filename))
assert.NilError(t, os.Remove(filename))
_, err = os.Stat(filename)
assert.Check(t, os.IsNotExist(err))
assert.NilError(t, file.Close())
})
}
func TestCIDFileCloseWithWrite(t *testing.T) {
+3
View File
@@ -248,5 +248,8 @@ func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*client
} else {
execOptions.DetachKeys = configFile.DetachKeys
}
if err := validateDetachKeys(execOpts.DetachKeys); err != nil {
return nil, err
}
return execOptions, nil
}
+22 -12
View File
@@ -92,21 +92,21 @@ TWO=2
},
{
options: withDefaultOpts(ExecOptions{Detach: true}),
configFile: configfile.ConfigFile{DetachKeys: "de"},
configFile: configfile.ConfigFile{DetachKeys: "ctrl-d,e"},
expected: client.ExecCreateOptions{
Cmd: []string{"command"},
DetachKeys: "de",
DetachKeys: "ctrl-d,e",
},
},
{
options: withDefaultOpts(ExecOptions{
Detach: true,
DetachKeys: "ab",
DetachKeys: "ctrl-a,b",
}),
configFile: configfile.ConfigFile{DetachKeys: "de"},
configFile: configfile.ConfigFile{DetachKeys: "ctrl-d,e"},
expected: client.ExecCreateOptions{
Cmd: []string{"command"},
DetachKeys: "ab",
DetachKeys: "ctrl-a,b",
},
},
{
@@ -147,13 +147,23 @@ TWO=2
}
}
func TestParseExecNoSuchFile(t *testing.T) {
execOpts := withDefaultOpts(ExecOptions{})
assert.Check(t, execOpts.EnvFile.Set("no-such-env-file"))
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
assert.ErrorContains(t, err, "no-such-env-file")
assert.Check(t, os.IsNotExist(err))
assert.Check(t, execConfig == nil)
func TestParseExecErrors(t *testing.T) {
t.Run("missing env-file", func(t *testing.T) {
execOpts := withDefaultOpts(ExecOptions{})
assert.Check(t, execOpts.EnvFile.Set("no-such-env-file"))
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
assert.ErrorContains(t, err, "no-such-env-file")
assert.Check(t, os.IsNotExist(err))
assert.Check(t, execConfig == nil)
})
t.Run("invalid detach keys", func(t *testing.T) {
execOpts := withDefaultOpts(ExecOptions{
DetachKeys: "shift-a",
})
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
assert.Check(t, is.ErrorContains(err, "invalid detach keys (shift-a):"))
assert.Check(t, is.Nil(execConfig))
})
}
func TestRunExec(t *testing.T) {
+18 -6
View File
@@ -30,6 +30,16 @@ func (r *readCloserWrapper) Close() error {
return r.closer()
}
func validateDetachKeys(keys string) error {
if keys == "" {
return nil
}
if _, err := term.ToBytes(keys); err != nil {
return invalidParameter(fmt.Errorf("invalid detach keys (%s): %w", keys, err))
}
return nil
}
// A hijackedIOStreamer handles copying input to and output from streams to the
// connection.
type hijackedIOStreamer struct {
@@ -82,13 +92,15 @@ func (h *hijackedIOStreamer) stream(ctx context.Context) error {
}
}
func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
func (h *hijackedIOStreamer) setupInput() (restore func(), _ error) {
if h.inputStream == nil || !h.tty {
// No need to setup input TTY.
// The restore func is a nop.
return func() {}, nil
}
if err := validateDetachKeys(h.detachKeys); err != nil {
return nil, err
}
if err := setRawTerminal(h.streams); err != nil {
return nil, fmt.Errorf("unable to set IO streams as raw terminal: %s", err)
}
@@ -103,11 +115,11 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
// Use default escape keys if an invalid sequence is given.
escapeKeys := defaultEscapeKeys
if h.detachKeys != "" {
customEscapeKeys, err := term.ToBytes(h.detachKeys)
var err error
escapeKeys, err = term.ToBytes(h.detachKeys)
if err != nil {
logrus.Warnf("invalid detach escape keys, using default: %s", err)
} else {
escapeKeys = customEscapeKeys
restore()
return nil, err
}
}
+6 -6
View File
@@ -1,4 +1,4 @@
// FIXME(vvoland): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package container
@@ -795,7 +795,7 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*network.EndpointSett
// and only a single network is specified, omit the endpoint-configuration
// on the client (the daemon will still create it when creating the container)
if i == 0 && len(copts.netMode.Value()) == 1 {
if ep == nil || reflect.DeepEqual(*ep, network.EndpointSettings{}) {
if ep == nil || reflect.ValueOf(*ep).IsZero() {
continue
}
}
@@ -902,7 +902,7 @@ func convertToStandardNotation(ports []string) ([]string, error) {
for _, publish := range ports {
if strings.Contains(publish, "=") {
params := map[string]string{"protocol": "tcp"}
for _, param := range strings.Split(publish, ",") {
for param := range strings.SplitSeq(publish, ",") {
k, v, ok := strings.Cut(param, "=")
if !ok || k == "" {
return optsList, fmt.Errorf("invalid publish opts format (should be name=value but got '%s')", param)
@@ -947,11 +947,11 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) {
if err != nil {
return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %w", v, err)
}
b := bytes.NewBuffer(nil)
if err := json.Compact(b, f); err != nil {
var b bytes.Buffer
if err := json.Compact(&b, f); err != nil {
return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %w", v, err)
}
securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
securityOpts[key] = "seccomp=" + b.String()
}
}
}
+8 -5
View File
@@ -140,6 +140,14 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
config.StdinOnce = false
}
detachKeys := runOpts.detachKeys
if detachKeys == "" {
detachKeys = dockerCli.ConfigFile().DetachKeys
}
if err := validateDetachKeys(runOpts.detachKeys); err != nil {
return err
}
containerID, err := createContainer(ctx, dockerCli, containerCfg, &runOpts.createOptions)
if err != nil {
return toStatusError(err)
@@ -172,11 +180,6 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
}()
}
if attach {
detachKeys := dockerCli.ConfigFile().DetachKeys
if runOpts.detachKeys != "" {
detachKeys = runOpts.detachKeys
}
// ctx should not be cancellable here, as this would kill the stream to the container
// and we want to keep the stream open until the process in the container exits or until
// the user forcefully terminates the CLI.
+8 -13
View File
@@ -16,8 +16,6 @@ import (
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/moby/moby/client/pkg/progress"
"github.com/moby/moby/client/pkg/streamformatter"
"github.com/spf13/pflag"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@@ -34,6 +32,11 @@ func TestRunValidateFlags(t *testing.T) {
args: []string{"--attach", "stdin", "--detach", "myimage"},
expectedErr: "conflicting options: cannot specify both --attach and --detach",
},
{
name: "with invalid --detach-keys",
args: []string{"--detach-keys", "shift-a", "myimage"},
expectedErr: "invalid detach keys (shift-a):",
},
} {
t.Run(tc.name, func(t *testing.T) {
cmd := newRunCommand(test.NewFakeCli(&fakeClient{}))
@@ -124,7 +127,7 @@ func TestRunAttach(t *testing.T) {
}
// end stream from "container" so that we'll detach
conn.Close()
assert.NilError(t, conn.Close())
select {
case cmdErr := <-cmdErrC:
@@ -204,7 +207,7 @@ func TestRunAttachTermination(t *testing.T) {
}
assert.NilError(t, syscall.Kill(syscall.Getpid(), syscall.SIGTERM))
conn.Close()
assert.NilError(t, conn.Close())
select {
case <-killCh:
@@ -240,20 +243,12 @@ func TestRunPullTermination(t *testing.T) {
_ = server.Close()
})
go func() {
id := test.RandomID()[:12] // short-ID
progressOutput := streamformatter.NewJSONProgressOutput(server, true)
for i := 0; i < 100; i++ {
for range 100 {
select {
case <-ctx.Done():
assert.NilError(t, server.Close(), "failed to close imageCreateFunc server")
return
default:
assert.NilError(t, progressOutput.WriteProgress(progress.Progress{
ID: id,
Message: "Downloading",
Current: int64(i),
Total: 100,
}))
time.Sleep(100 * time.Millisecond)
}
}
+1 -2
View File
@@ -11,8 +11,7 @@ import (
)
func TestForwardSignals(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := t.Context()
called := make(chan struct{})
apiClient := &fakeClient{containerKillFunc: func(ctx context.Context, container string, options client.ContainerKillOptions) (client.ContainerKillResult, error) {
+8 -5
View File
@@ -70,6 +70,14 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
ctx, cancelFun := context.WithCancel(ctx)
defer cancelFun()
detachKeys := opts.DetachKeys
if detachKeys == "" {
detachKeys = dockerCli.ConfigFile().DetachKeys
}
if err := validateDetachKeys(detachKeys); err != nil {
return err
}
switch {
case opts.Attach || opts.OpenStdin:
// We're going to attach to a container.
@@ -93,11 +101,6 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
defer signal.StopCatch(sigc)
}
detachKeys := dockerCli.ConfigFile().DetachKeys
if opts.DetachKeys != "" {
detachKeys = opts.DetachKeys
}
options := client.ContainerAttachOptions{
Stream: true,
Stdin: opts.OpenStdin && c.Container.Config.OpenStdin,
+38
View File
@@ -0,0 +1,38 @@
package container
import (
"io"
"testing"
"github.com/docker/cli/internal/test"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestStartValidateFlags(t *testing.T) {
for _, tc := range []struct {
name string
args []string
expectedErr string
}{
{
name: "with invalid --detach-keys",
args: []string{"--detach-keys", "shift-a", "myimage"},
expectedErr: "invalid detach keys (shift-a):",
},
} {
t.Run(tc.name, func(t *testing.T) {
cmd := newStartCommand(test.NewFakeCli(&fakeClient{}))
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
if tc.expectedErr != "" {
assert.Check(t, is.ErrorContains(err, tc.expectedErr))
} else {
assert.Check(t, is.Nil(err))
}
})
}
}
+4 -1
View File
@@ -1,3 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package container
import (
@@ -337,7 +340,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
return err
}
for _, line := range strings.Split(statsTextBuffer.String(), "\n") {
for line := range strings.SplitSeq(statsTextBuffer.String(), "\n") {
// In case the new text is shorter than the one we are writing over,
// we'll append the "erase line" escape sequence to clear the remaining text.
_, _ = fmt.Fprintln(&statsTextBuffer, line, "\033[K")
+4 -1
View File
@@ -1,3 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package container
import (
@@ -60,7 +63,7 @@ func initTtySize(ctx context.Context, cli command.Cli, id string, isExec bool, r
if err := rTTYfunc(ctx, cli, id, isExec); err != nil {
go func() {
var err error
for retry := 0; retry < 10; retry++ {
for retry := range 10 {
time.Sleep(time.Duration(retry+1) * 10 * time.Millisecond)
if err = rTTYfunc(ctx, cli, id, isExec); err == nil {
break
+2 -3
View File
@@ -6,6 +6,7 @@ package command
import (
"encoding/json"
"errors"
"maps"
"github.com/docker/cli/cli/context/store"
)
@@ -23,9 +24,7 @@ func (dc DockerContext) MarshalJSON() ([]byte, error) {
s["Description"] = dc.Description
}
if dc.AdditionalFields != nil {
for k, v := range dc.AdditionalFields {
s[k] = v
}
maps.Copy(s, dc.AdditionalFields)
}
return json.Marshal(s)
}
+1 -1
View File
@@ -350,7 +350,7 @@ func DisplayablePorts(ports []container.PortSummary) string {
last uint16
}
groupMap := make(map[string]*portGroup)
var result []string //nolint:prealloc
var result []string
var hostMappings []string
var groupMapKeys []string
sort.Slice(ports, func(i, j int) bool {
+13 -17
View File
@@ -4,8 +4,10 @@
package formatter
import (
"reflect"
"testing"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
type dummy struct{}
@@ -45,24 +47,18 @@ var dummyExpected = map[string]any{
func TestMarshalMap(t *testing.T) {
d := dummy{}
m, err := marshalMap(&d)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(dummyExpected, m) {
t.Fatalf("expected %+v, got %+v",
dummyExpected, m)
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(m, dummyExpected))
}
func TestMarshalMapBad(t *testing.T) {
if _, err := marshalMap(nil); err == nil {
t.Fatal("expected an error (argument is nil)")
}
if _, err := marshalMap(dummy{}); err == nil {
t.Fatal("expected an error (argument is non-pointer)")
}
_, err := marshalMap(nil)
assert.Check(t, is.Error(err, "expected a pointer to a struct, got invalid"), "expected an error (argument is nil)")
_, err = marshalMap(dummy{})
assert.Check(t, is.Error(err, "expected a pointer to a struct, got struct"), "expected an error (argument is non-pointer)")
x := 42
if _, err := marshalMap(&x); err == nil {
t.Fatal("expected an error (argument is a pointer to non-struct)")
}
_, err = marshalMap(&x)
assert.Check(t, is.Error(err, "expected a pointer to a struct, got a pointer to int"), "expected an error (argument is a pointer to non-struct)")
}
@@ -25,7 +25,7 @@ func (b *buffer) Write(buf []byte) (written int, err error) {
m := len(buf)
if n+m <= cap(b.a) {
b.a = b.a[0 : n+m]
for i := 0; i < m; i++ {
for i := range m {
b.a[n+i] = buf[i]
}
} else {
@@ -669,7 +669,7 @@ func BenchmarkTable(b *testing.B) {
for i := 0; i < b.N; i++ {
w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings
// Write the line h times.
for j := 0; j < h; j++ {
for range h {
w.Write(line)
}
w.Flush()
@@ -681,7 +681,7 @@ func BenchmarkTable(b *testing.B) {
w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings
for i := 0; i < b.N; i++ {
// Write the line h times.
for j := 0; j < h; j++ {
for range h {
w.Write(line)
}
w.Flush()
@@ -701,7 +701,7 @@ func BenchmarkPyramid(b *testing.B) {
for i := 0; i < b.N; i++ {
w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings
// Write increasing prefixes of that line.
for j := 0; j < x; j++ {
for j := range x {
w.Write(line[:j*2])
w.Write([]byte{'\n'})
}
@@ -723,7 +723,7 @@ func BenchmarkRagged(b *testing.B) {
for i := 0; i < b.N; i++ {
w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings
// Write the lines in turn h times.
for j := 0; j < h; j++ {
for j := range h {
w.Write(lines[j%len(lines)])
w.Write([]byte{'\n'})
}
+1 -1
View File
@@ -60,7 +60,7 @@ func TestResolveWithCache(t *testing.T) {
idResolver := New(apiClient, false)
ctx := context.Background()
for i := 0; i < 2; i++ {
for range 2 {
id, err := idResolver.Resolve(ctx, swarm.Node{}, "nodeID")
assert.NilError(t, err)
assert.Check(t, is.Equal("node-foo", id))
+1 -1
View File
@@ -480,7 +480,7 @@ func Compress(buildCtx io.ReadCloser) (io.ReadCloser, error) {
pipeReader, pipeWriter := io.Pipe()
go func() {
compressWriter, err := compression.CompressStream(pipeWriter, archive.Gzip)
compressWriter, err := compression.CompressStream(pipeWriter, compression.Gzip)
if err != nil {
_ = pipeWriter.CloseWithError(err)
}
+4 -3
View File
@@ -190,7 +190,7 @@ func (f *fakeBuild) build(_ context.Context, buildContext io.Reader, options cli
func (f *fakeBuild) headers(t *testing.T) []*tar.Header {
t.Helper()
headers := []*tar.Header{}
var headers []*tar.Header
for {
hdr, err := f.context.Next()
switch err {
@@ -206,8 +206,9 @@ func (f *fakeBuild) headers(t *testing.T) []*tar.Header {
func (f *fakeBuild) filenames(t *testing.T) []string {
t.Helper()
names := []string{}
for _, header := range f.headers(t) {
h := f.headers(t)
names := make([]string, 0, len(h))
for _, header := range h {
names = append(names, header.Name)
}
sort.Strings(names)
+1 -4
View File
@@ -389,10 +389,7 @@ func generateLegend(out tui.Output, width uint) string {
}
legend += legendSb371.String()
r := int(width) - tui.Width(legend)
if r < 0 {
r = 0
}
r := max(int(width)-tui.Width(legend), 0)
legend = strings.Repeat(" ", r) + legend
return legend
}
+6 -4
View File
@@ -1,9 +1,13 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package manifest
import (
"context"
"fmt"
"path/filepath"
"slices"
"github.com/containerd/errdefs"
"github.com/docker/cli/cli"
@@ -164,10 +168,8 @@ func runManifestAnnotate(dockerCLI command.Cli, opts annotateOptions) error {
}
func appendIfUnique(list []string, str string) []string {
for _, s := range list {
if s == str {
return list
}
if slices.Contains(list, str) {
return list
}
return append(list, str)
}
+12 -5
View File
@@ -1,9 +1,9 @@
package node
import (
"bytes"
"encoding/base64"
"fmt"
"reflect"
"strings"
"github.com/docker/cli/cli/command/formatter"
@@ -170,15 +170,23 @@ func (c *nodeContext) ManagerStatus() string {
}
func (c *nodeContext) TLSStatus() string {
if c.info.Cluster == nil || reflect.DeepEqual(c.info.Cluster.TLSInfo, swarm.TLSInfo{}) || reflect.DeepEqual(c.n.Description.TLSInfo, swarm.TLSInfo{}) {
if c.info.Cluster == nil || isEmptyTLSInfo(c.info.Cluster.TLSInfo) || isEmptyTLSInfo(c.n.Description.TLSInfo) {
return "Unknown"
}
if reflect.DeepEqual(c.n.Description.TLSInfo, c.info.Cluster.TLSInfo) {
if equalTLSInfo(c.n.Description.TLSInfo, c.info.Cluster.TLSInfo) {
return "Ready"
}
return "Needs Rotation"
}
func isEmptyTLSInfo(t swarm.TLSInfo) bool {
return t.TrustRoot == "" && len(t.CertIssuerSubject) == 0 && len(t.CertIssuerPublicKey) == 0
}
func equalTLSInfo(t, o swarm.TLSInfo) bool {
return t.TrustRoot == o.TrustRoot && bytes.Equal(t.CertIssuerSubject, o.CertIssuerSubject) && bytes.Equal(t.CertIssuerPublicKey, o.CertIssuerPublicKey)
}
func (c *nodeContext) EngineVersion() string {
return c.n.Description.Engine.EngineVersion
}
@@ -320,8 +328,7 @@ func (ctx *nodeInspectContext) EngineVersion() string {
}
func (ctx *nodeInspectContext) HasTLSInfo() bool {
tlsInfo := ctx.Node.Description.TLSInfo
return !reflect.DeepEqual(tlsInfo, swarm.TLSInfo{})
return !isEmptyTLSInfo(ctx.Node.Description.TLSInfo)
}
func (ctx *nodeInspectContext) TLSInfoTrustRoot() string {
+5 -3
View File
@@ -1,9 +1,13 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package node
import (
"context"
"errors"
"fmt"
"maps"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
@@ -99,9 +103,7 @@ func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) error {
}
if flags.Changed(flagLabelAdd) {
labels := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetSlice()
for k, v := range opts.ConvertKVStringsToMap(labels) {
spec.Annotations.Labels[k] = v
}
maps.Copy(spec.Annotations.Labels, opts.ConvertKVStringsToMap(labels))
}
if flags.Changed(flagLabelRemove) {
keys := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetSlice()
+3 -7
View File
@@ -144,8 +144,8 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
}
}
argPassword = strings.TrimSpace(argPassword)
if argPassword == "" {
isEmpty := strings.TrimSpace(argPassword) == ""
if isEmpty {
restoreInput, err := prompt.DisableInputEcho(cli.In())
if err != nil {
return registrytypes.AuthConfig{}, err
@@ -200,7 +200,7 @@ func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (strin
return "", err
}
encodedAuth, err := authconfig.Encode(registrytypes.AuthConfig{
return authconfig.Encode(registrytypes.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.ServerAddress,
@@ -210,10 +210,6 @@ func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (strin
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
})
if err != nil {
return "", err
}
return encodedAuth, nil
}
// getAuthConfigKey special-cases using the full index address of the official
+39 -6
View File
@@ -1,6 +1,7 @@
package registry
import (
"bytes"
"context"
"errors"
"fmt"
@@ -88,6 +89,38 @@ func verifyLoginFlags(flags *pflag.FlagSet, opts loginOptions) error {
return nil
}
// readSecretFromStdin reads the secret from r and returns it as a string.
// It trims terminal line-endings (LF, CRLF, or CR), which may be added when
// inputting interactively or piping input. The value is otherwise treated as
// opaque, preserving any other whitespace, including newlines, per [NIST SP 800-63B §5.1.1.2].
// Note that trimming whitespace may still happen elsewhere (see [NIST SP 800-63B (revision 4) §3.1.1.2]);
//
// > Verifiers **MAY** make limited allowances for mistyping (e.g., removing
// > leading and trailing whitespace characters before verification, allowing
// > the verification of passwords with differing cases for the leading character)
//
// [NIST SP 800-63B §5.1.1.2]: https://pages.nist.gov/800-63-3/sp800-63b.html#memsecretver
// [NIST SP 800-63B (revision 4) §3.1.1.2]: https://pages.nist.gov/800-63-4/sp800-63b.html#passwordver
func readSecretFromStdin(r io.Reader) (string, error) {
b, err := io.ReadAll(r)
if err != nil {
return "", err
}
if len(b) == 0 {
return "", nil
}
for _, eol := range [][]byte{[]byte("\r\n"), []byte("\n"), []byte("\r")} {
var ok bool
b, ok = bytes.CutSuffix(b, eol)
if ok {
break
}
}
return string(b), nil
}
func verifyLoginOptions(dockerCLI command.Streams, opts *loginOptions) error {
if opts.password != "" {
_, _ = fmt.Fprintln(dockerCLI.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
@@ -97,14 +130,14 @@ func verifyLoginOptions(dockerCLI command.Streams, opts *loginOptions) error {
if opts.user == "" {
return errors.New("username is empty")
}
contents, err := io.ReadAll(dockerCLI.In())
p, err := readSecretFromStdin(dockerCLI.In())
if err != nil {
return err
}
opts.password = strings.TrimSuffix(string(contents), "\n")
opts.password = strings.TrimSuffix(opts.password, "\r")
if strings.TrimSpace(p) == "" {
return errors.New("password is empty")
}
opts.password = p
}
return nil
}
@@ -197,7 +230,7 @@ func loginUser(ctx context.Context, dockerCLI command.Cli, opts loginOptions, de
// will hit this if you attempt docker login from mintty where stdin
// is a pipe, not a character based console.
if (opts.user == "" || opts.password == "") && !dockerCLI.In().IsTerminal() {
return "", errors.New("error: cannot perform an interactive login from a non TTY device")
return "", errors.New("error: cannot perform an interactive login from a non-TTY device")
}
// If we're logging into the index server and the user didn't provide a username or password, use the device flow
+134 -29
View File
@@ -1,15 +1,18 @@
package registry
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"path/filepath"
"testing"
"testing/iotest"
"time"
"github.com/creack/pty"
"github.com/docker/cli/cli/config/configfile"
configtypes "github.com/docker/cli/cli/config/types"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/internal/prompt"
@@ -19,7 +22,6 @@ import (
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/fs"
)
const (
@@ -71,8 +73,9 @@ func TestLoginWithCredStoreCreds(t *testing.T) {
},
}
ctx := context.Background()
tmpDir := t.TempDir()
cli := test.NewFakeCli(&fakeClient{})
cli.ConfigFile().Filename = filepath.Join(t.TempDir(), "config.json")
cli.SetConfigFile(configfile.New(filepath.Join(tmpDir, "config.json")))
for _, tc := range testCases {
_, err := loginWithStoredCredentials(ctx, cli, tc.inputAuthConfig)
if tc.expectedErrMsg != "" {
@@ -91,6 +94,7 @@ func TestRunLogin(t *testing.T) {
testCases := []struct {
doc string
priorCredentials map[string]configtypes.AuthConfig
stdIn string
input loginOptions
expectedCredentials map[string]configtypes.AuthConfig
expectedErr string
@@ -127,7 +131,7 @@ func TestRunLogin(t *testing.T) {
input: loginOptions{
serverAddress: "reg1",
},
expectedErr: "error: cannot perform an interactive login from a non TTY device",
expectedErr: "error: cannot perform an interactive login from a non-TTY device",
},
{
doc: "store valid username and password",
@@ -286,20 +290,125 @@ func TestRunLogin(t *testing.T) {
},
},
},
{
doc: "password stdin empty",
priorCredentials: map[string]configtypes.AuthConfig{},
input: loginOptions{
serverAddress: "reg1",
user: "my-username",
passwordStdin: true,
},
expectedErr: `password is empty`,
expectedCredentials: map[string]configtypes.AuthConfig{
"reg1": {
Username: "my-username",
ServerAddress: "reg1",
},
},
},
{
doc: "password stdin read error",
priorCredentials: map[string]configtypes.AuthConfig{},
input: loginOptions{
serverAddress: "reg1",
user: "my-username",
passwordStdin: true,
},
expectedErr: `TEST_READ_ERR`,
expectedCredentials: map[string]configtypes.AuthConfig{
"reg1": {
Username: "my-username",
ServerAddress: "reg1",
},
},
},
{
doc: "password stdin with line-endings",
priorCredentials: map[string]configtypes.AuthConfig{},
stdIn: "my password\r\n",
input: loginOptions{
serverAddress: "reg1",
user: "my-username",
passwordStdin: true,
},
expectedCredentials: map[string]configtypes.AuthConfig{
"reg1": {
Username: "my-username",
Password: "my password",
ServerAddress: "reg1",
},
},
},
{
doc: "password with leading and trailing spaces",
priorCredentials: map[string]configtypes.AuthConfig{},
input: loginOptions{
serverAddress: "reg1",
user: "my-username",
password: " my password with spaces ",
},
expectedCredentials: map[string]configtypes.AuthConfig{
"reg1": {
Username: "my-username",
Password: " my password with spaces ",
ServerAddress: "reg1",
},
},
},
{
doc: "password stdin with line-endings",
priorCredentials: map[string]configtypes.AuthConfig{},
stdIn: " my password with spaces \r\n",
input: loginOptions{
serverAddress: "reg1",
user: "my-username",
passwordStdin: true,
},
expectedCredentials: map[string]configtypes.AuthConfig{
"reg1": {
Username: "my-username",
Password: " my password with spaces ",
ServerAddress: "reg1",
},
},
},
{
doc: "password stdin with multiple line-endings",
priorCredentials: map[string]configtypes.AuthConfig{},
stdIn: " my password\nwith spaces \r\n\r\n",
input: loginOptions{
serverAddress: "reg1",
user: "my-username",
passwordStdin: true,
},
expectedCredentials: map[string]configtypes.AuthConfig{
"reg1": {
Username: "my-username",
Password: " my password\nwith spaces \r\n",
ServerAddress: "reg1",
},
},
},
}
for _, tc := range testCases {
t.Run(tc.doc, func(t *testing.T) {
tmpFile := fs.NewFile(t, "test-run-login")
defer tmpFile.Remove()
tmpDir := t.TempDir()
cfg := configfile.New(filepath.Join(tmpDir, "config.json"))
cli := test.NewFakeCli(&fakeClient{})
configfile := cli.ConfigFile()
configfile.Filename = tmpFile.Path()
cli.SetConfigFile(cfg)
if tc.input.passwordStdin {
if tc.expectedErr == "TEST_READ_ERR" {
cli.SetIn(streams.NewIn(io.NopCloser(iotest.ErrReader(errors.New(tc.expectedErr)))))
} else {
cli.SetIn(streams.NewIn(io.NopCloser(bytes.NewBufferString(tc.stdIn))))
}
}
for _, priorCred := range tc.priorCredentials {
assert.NilError(t, configfile.GetCredentialsStore(priorCred.ServerAddress).Store(priorCred))
assert.NilError(t, cfg.GetCredentialsStore(priorCred.ServerAddress).Store(priorCred))
}
storedCreds, err := configfile.GetAllCredentials()
storedCreds, err := cfg.GetAllCredentials()
assert.NilError(t, err)
assert.DeepEqual(t, storedCreds, tc.priorCredentials)
@@ -310,7 +419,7 @@ func TestRunLogin(t *testing.T) {
}
assert.NilError(t, loginErr)
outputCreds, err := configfile.GetAllCredentials()
outputCreds, err := cfg.GetAllCredentials()
assert.Check(t, err)
assert.DeepEqual(t, outputCreds, tc.expectedCredentials)
})
@@ -334,19 +443,19 @@ func TestLoginNonInteractive(t *testing.T) {
doc: "error - w/o user w/o pass ",
username: false,
password: false,
expectedErr: "error: cannot perform an interactive login from a non TTY device",
expectedErr: "error: cannot perform an interactive login from a non-TTY device",
},
{
doc: "error - w/ user w/o pass",
username: true,
password: false,
expectedErr: "error: cannot perform an interactive login from a non TTY device",
expectedErr: "error: cannot perform an interactive login from a non-TTY device",
},
{
doc: "error - w/o user w/ pass",
username: false,
password: true,
expectedErr: "error: cannot perform an interactive login from a non TTY device",
expectedErr: "error: cannot perform an interactive login from a non-TTY device",
},
}
@@ -356,11 +465,10 @@ func TestLoginNonInteractive(t *testing.T) {
for _, registryAddr := range registries {
for _, tc := range testCases {
t.Run(tc.doc, func(t *testing.T) {
tmpFile := fs.NewFile(t, "test-run-login")
defer tmpFile.Remove()
tmpDir := t.TempDir()
cfg := configfile.New(filepath.Join(tmpDir, "config.json"))
cli := test.NewFakeCli(&fakeClient{})
cfg := cli.ConfigFile()
cfg.Filename = tmpFile.Path()
cli.SetConfigFile(cfg)
options := loginOptions{
serverAddress: registryAddr,
}
@@ -403,13 +511,13 @@ func TestLoginNonInteractive(t *testing.T) {
doc: "error - w/ user w/o pass",
username: true,
password: false,
expectedErr: "error: cannot perform an interactive login from a non TTY device",
expectedErr: "error: cannot perform an interactive login from a non-TTY device",
},
{
doc: "error - w/o user w/ pass",
username: false,
password: true,
expectedErr: "error: cannot perform an interactive login from a non TTY device",
expectedErr: "error: cannot perform an interactive login from a non-TTY device",
},
}
@@ -419,11 +527,10 @@ func TestLoginNonInteractive(t *testing.T) {
for _, registryAddr := range registries {
for _, tc := range testCases {
t.Run(tc.doc, func(t *testing.T) {
tmpFile := fs.NewFile(t, "test-run-login")
defer tmpFile.Remove()
tmpDir := t.TempDir()
cfg := configfile.New(filepath.Join(tmpDir, "config.json"))
cli := test.NewFakeCli(&fakeClient{})
cfg := cli.ConfigFile()
cfg.Filename = tmpFile.Path()
cli.SetConfigFile(cfg)
serverAddress := registryAddr
if serverAddress == "" {
serverAddress = "https://index.docker.io/v1/"
@@ -465,17 +572,15 @@ func TestLoginTermination(t *testing.T) {
_ = p.Close()
})
tmpDir := t.TempDir()
cfg := configfile.New(filepath.Join(tmpDir, "config.json"))
cli := test.NewFakeCli(&fakeClient{}, func(fc *test.FakeCli) {
fc.SetOut(streams.NewOut(tty))
fc.SetIn(streams.NewIn(tty))
})
tmpFile := fs.NewFile(t, "test-login-termination")
defer tmpFile.Remove()
cli.SetConfigFile(cfg)
configFile := cli.ConfigFile()
configFile.Filename = tmpFile.Path()
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(t.Context())
t.Cleanup(cancel)
runErr := make(chan error)
+4 -1
View File
@@ -3,6 +3,7 @@ package command_test
import (
"bytes"
"path"
"path/filepath"
"testing"
"github.com/docker/cli/cli/command"
@@ -58,7 +59,9 @@ func TestGetDefaultAuthConfig(t *testing.T) {
expectedAuthConfig: testAuthConfigs[1],
},
}
cfg := configfile.New("filename")
tmpDir := t.TempDir()
cfg := configfile.New(filepath.Join(tmpDir, "cli-config.json"))
for _, authConfig := range testAuthConfigs {
assert.Check(t, cfg.GetCredentialsStore(authConfig.ServerAddress).Store(configtypes.AuthConfig{
Username: authConfig.Username,
+3 -6
View File
@@ -108,7 +108,7 @@ func TestServiceListServiceStatus(t *testing.T) {
},
}
matrix := make([]testCase, 0)
matrix := make([]testCase, 0, len(tests))
for _, quiet := range []bool{false, true} {
for _, tc := range tests {
if quiet {
@@ -191,10 +191,7 @@ func generateServices(t *testing.T, opts clusterOpts) client.ServiceListResult {
t.Helper()
// Can't have more global tasks than nodes
globalTasks := opts.runningTasks
if globalTasks > opts.activeNodes {
globalTasks = opts.activeNodes
}
globalTasks := min(opts.runningTasks, opts.activeNodes)
return client.ServiceListResult{
Items: []swarm.Service{
*builders.Service(
@@ -279,7 +276,7 @@ func generateNodes(t *testing.T, activeNodes uint64) client.NodeListResult {
t.Helper()
nodes := client.NodeListResult{}
var i uint64
for i = 0; i < activeNodes; i++ {
for i = range activeNodes {
nodes.Items = append(nodes.Items, swarm.Node{
ID: fmt.Sprintf("node-ready-%d", i),
Status: swarm.NodeStatus{State: swarm.NodeStateReady},
+2 -2
View File
@@ -905,9 +905,9 @@ func addServiceFlags(flags *pflag.FlagSet, options *serviceOptions, defaultFlagV
flags.Int64Var(&options.resources.limitPids, flagLimitPids, 0, "Limit maximum number of processes (default 0 = unlimited)")
flags.SetAnnotation(flagLimitPids, "version", []string{"1.41"})
flags.Var(&options.resources.swapBytes, flagSwapBytes, "Swap Bytes (-1 for unlimited)")
flags.SetAnnotation(flagLimitPids, "version", []string{"1.52"})
flags.SetAnnotation(flagSwapBytes, "version", []string{"1.52"})
flags.Int64Var(&options.resources.memSwappiness, flagMemSwappiness, -1, "Tune memory swappiness (0-100), -1 to reset to default")
flags.SetAnnotation(flagLimitPids, "version", []string{"1.52"})
flags.SetAnnotation(flagMemSwappiness, "version", []string{"1.52"})
flags.Var(&options.stopGrace, flagStopGracePeriod, flagDesc(flagStopGracePeriod, "Time to wait before force killing a container (ns|us|ms|s|m|h)"))
flags.Var(&options.replicas, flagReplicas, "Number of tasks")
+4 -4
View File
@@ -1,3 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package progress
import (
@@ -662,10 +665,7 @@ func (u *replicatedJobProgressUpdater) writeOverallProgress(active, completed in
})
// actualDesired is the lesser of MaxConcurrent, or the remaining tasks
actualDesired := u.total - completed
if actualDesired > u.concurrent {
actualDesired = u.concurrent
}
actualDesired := min(u.total-completed, u.concurrent)
_ = u.progressOut.WriteProgress(progress.Progress{
ID: "active tasks",
+12 -13
View File
@@ -80,7 +80,7 @@ func TestReplicatedProgressUpdaterOneReplica(t *testing.T) {
service: service,
}
tasks := []swarm.Task{}
var tasks []swarm.Task //nolint:prealloc // no need to over-complicate things.
ut.testUpdater(tasks, false,
[]progress.Progress{
@@ -193,7 +193,7 @@ func TestReplicatedProgressUpdaterManyReplicas(t *testing.T) {
service: service,
}
tasks := []swarm.Task{}
var tasks []swarm.Task
// No per-task progress bars because there are too many replicas
ut.testUpdater(tasks, false,
@@ -248,7 +248,7 @@ func TestGlobalProgressUpdaterOneNode(t *testing.T) {
service: service,
}
tasks := []swarm.Task{}
var tasks []swarm.Task //nolint:prealloc // no need to over-complicate things.
ut.testUpdater(tasks, false,
[]progress.Progress{
@@ -362,7 +362,7 @@ func TestGlobalProgressUpdaterManyNodes(t *testing.T) {
ut.activeNodes[strconv.Itoa(i)] = struct{}{}
}
tasks := []swarm.Task{}
var tasks []swarm.Task
ut.testUpdater(tasks, false,
[]progress.Progress{
@@ -459,9 +459,8 @@ func TestReplicatedJobProgressUpdaterSmall(t *testing.T) {
})
// wipe the old tasks out of the list
tasks = []swarm.Task{}
tasks = append(tasks,
swarm.Task{
tasks = []swarm.Task{ //nolint:prealloc // no need to over-complicate things.
{
ID: "task1",
Slot: 0,
NodeID: "",
@@ -469,7 +468,7 @@ func TestReplicatedJobProgressUpdaterSmall(t *testing.T) {
Status: swarm.TaskStatus{State: swarm.TaskStateNew},
JobIteration: &swarm.Version{Index: service.JobStatus.JobIteration.Index},
},
swarm.Task{
{
ID: "task2",
Slot: 1,
NodeID: "",
@@ -477,7 +476,7 @@ func TestReplicatedJobProgressUpdaterSmall(t *testing.T) {
Status: swarm.TaskStatus{State: swarm.TaskStateNew},
JobIteration: &swarm.Version{Index: service.JobStatus.JobIteration.Index},
},
)
}
ut.testUpdater(tasks, false, []progress.Progress{
{ID: "1/5", Action: "new ", Current: 1, Total: 10, HideCounts: true},
{ID: "2/5", Action: "new ", Current: 1, Total: 10, HideCounts: true},
@@ -644,7 +643,7 @@ func TestReplicatedJobProgressUpdaterLarge(t *testing.T) {
service: service,
}
tasks := []swarm.Task{}
var tasks []swarm.Task
// see the comments in TestReplicatedJobProgressUpdaterSmall for why
// we write this out twice.
@@ -741,7 +740,7 @@ func TestGlobalJobProgressUpdaterSmall(t *testing.T) {
service: service,
}
tasks := []swarm.Task{
tasks := []swarm.Task{ //nolint:prealloc // no need to over-complicate things.
{
ID: "oldtask1",
DesiredState: swarm.TaskStateComplete,
@@ -856,7 +855,7 @@ func TestGlobalJobProgressUpdaterLarge(t *testing.T) {
}
activeNodes := map[string]struct{}{}
for i := 0; i < 50; i++ {
for i := range 50 {
activeNodes[fmt.Sprintf("node%v", i)] = struct{}{}
}
@@ -871,7 +870,7 @@ func TestGlobalJobProgressUpdaterLarge(t *testing.T) {
service: service,
}
tasks := []swarm.Task{}
var tasks []swarm.Task //nolint:prealloc // no need to over-complicate things.
for nodeID := range activeNodes {
tasks = append(tasks, swarm.Task{
ID: "task" + nodeID,
+6 -16
View File
@@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"maps"
"net/netip"
"slices"
"sort"
@@ -551,12 +552,7 @@ func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) {
}
func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
for _, flag := range fields {
if flags.Changed(flag) {
return true
}
}
return false
return slices.ContainsFunc(fields, flags.Changed)
}
func addGenericResources(flags *pflag.FlagSet, spec *swarm.TaskSpec) error {
@@ -685,9 +681,7 @@ func updateContainerLabels(flags *pflag.FlagSet, field *map[string]string) {
}
values := flags.Lookup(flagContainerLabelAdd).Value.(*opts.ListOpts).GetSlice()
for key, value := range opts.ConvertKVStringsToMap(values) {
(*field)[key] = value
}
maps.Copy((*field), opts.ConvertKVStringsToMap(values))
}
}
@@ -704,9 +698,7 @@ func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
}
values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetSlice()
for key, value := range opts.ConvertKVStringsToMap(values) {
(*field)[key] = value
}
maps.Copy((*field), opts.ConvertKVStringsToMap(values))
}
}
@@ -723,9 +715,7 @@ func updateSysCtls(flags *pflag.FlagSet, field *map[string]string) {
}
values := flags.Lookup(flagSysCtlAdd).Value.(*opts.ListOpts).GetSlice()
for key, value := range opts.ConvertKVStringsToMap(values) {
(*field)[key] = value
}
maps.Copy((*field), opts.ConvertKVStringsToMap(values))
}
}
@@ -1333,7 +1323,7 @@ func updateNetworks(ctx context.Context, apiClient client.NetworkAPIClient, flag
}
existingNetworks := make(map[string]struct{})
var newNetworks []swarm.NetworkAttachmentConfig //nolint:prealloc
var newNetworks []swarm.NetworkAttachmentConfig
for _, nw := range specNetworks {
if _, exists := idsToRemove[nw.Target]; exists {
continue
+17 -17
View File
@@ -4,8 +4,8 @@ import (
"context"
"fmt"
"net/netip"
"reflect"
"sort"
"strconv"
"testing"
"time"
@@ -368,23 +368,23 @@ func TestUpdateHealthcheckTable(t *testing.T) {
err: "--no-healthcheck conflicts with --health-* options",
},
}
for i, c := range testCases {
flags := newUpdateCommand(nil).Flags()
for _, flag := range c.flags {
flags.Set(flag[0], flag[1])
}
cspec := &swarm.ContainerSpec{
Healthcheck: c.initial,
}
err := updateHealthcheck(flags, cspec)
if c.err != "" {
assert.Error(t, err, c.err)
} else {
assert.NilError(t, err)
if !reflect.DeepEqual(cspec.Healthcheck, c.expected) {
t.Errorf("incorrect result for test %d, expected health config:\n\t%#v\ngot:\n\t%#v", i, c.expected, cspec.Healthcheck)
for i, tc := range testCases {
t.Run(strconv.Itoa(i), func(t *testing.T) {
flags := newUpdateCommand(nil).Flags()
for _, flag := range tc.flags {
assert.Check(t, flags.Set(flag[0], flag[1]))
}
}
cspec := &swarm.ContainerSpec{
Healthcheck: tc.initial,
}
err := updateHealthcheck(flags, cspec)
if tc.err != "" {
assert.Error(t, err, tc.err)
} else {
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(cspec.Healthcheck, tc.expected))
}
})
}
}
+2 -3
View File
@@ -27,7 +27,6 @@ func loadComposeFile(streams command.Streams, opts deployOptions) (*composetypes
return nil, err
}
dicts := getDictsFrom(configDetails.ConfigFiles)
config, err := loader.Load(configDetails)
if err != nil {
var fpe *loader.ForbiddenPropertiesError
@@ -39,6 +38,7 @@ func loadComposeFile(streams command.Streams, opts deployOptions) (*composetypes
return nil, err
}
dicts := getDictsFrom(configDetails.ConfigFiles)
unsupportedProperties := loader.GetUnsupportedProperties(dicts...)
if len(unsupportedProperties) > 0 {
_, _ = fmt.Fprintf(streams.Err(), "Ignoring unsupported options: %s\n\n",
@@ -65,8 +65,7 @@ func loadComposeFile(streams command.Streams, opts deployOptions) (*composetypes
}
func getDictsFrom(configFiles []composetypes.ConfigFile) []map[string]any {
dicts := []map[string]any{}
dicts := make([]map[string]any, 0, len(configFiles))
for _, configFile := range configFiles {
dicts = append(dicts, configFile.Config)
}
+1 -1
View File
@@ -110,7 +110,7 @@ func (*ExternalCAOption) Type() string {
// String returns a string repr of this option.
func (m *ExternalCAOption) String() string {
externalCAs := []string{}
externalCAs := make([]string, 0, len(m.values))
for _, externalCA := range m.values {
repr := fmt.Sprintf("%s: %s", externalCA.Protocol, externalCA.URL)
externalCAs = append(externalCAs, repr)
+2 -2
View File
@@ -133,12 +133,12 @@ func prettyPrintEvent(out io.Writer, event events.Message) error {
_, _ = fmt.Fprintf(out, "%s %s %s", event.Type, event.Action, event.Actor.ID)
if len(event.Actor.Attributes) > 0 {
var attrs []string
var keys []string
keys := make([]string, 0, len(event.Actor.Attributes))
for k := range event.Actor.Attributes {
keys = append(keys, k)
}
sort.Strings(keys)
attrs := make([]string, 0, len(keys))
for _, k := range keys {
v := event.Actor.Attributes[k]
attrs = append(attrs, k+"="+v)
+7
View File
@@ -397,6 +397,13 @@ func prettyPrintServerInfo(streams command.Streams, info *dockerInfo) {
}
}
if info.NRI != nil {
fprintln(output, " NRI:")
for _, v := range info.NRI.Info {
fprintf(output, " %s: %s\n", v[0], v[1])
}
}
fprintln(output)
for _, w := range info.Warnings {
fprintln(streams.Err(), w)
+6
View File
@@ -126,6 +126,12 @@ var sampleInfoNoSwarm = system.Info{
{"ReloadedAt", "2025-07-16T16:59:14Z"},
},
},
NRI: &system.NRIInfo{
Info: [][2]string{
{"plugin-path", "/usr/libexec/docker/nri-plugins"},
{"plugin-config-path", "/etc/docker/nri/conf.d"},
},
},
CDISpecDirs: []string{"/etc/cdi", "/var/run/cdi"},
}
+3
View File
@@ -54,4 +54,7 @@ Server:
Base: 10.123.0.0/16, Size: 24
Firewall Backend: nftables+firewalld
ReloadedAt: 2025-07-16T16:59:14Z
NRI:
plugin-path: /usr/libexec/docker/nri-plugins
plugin-config-path: /etc/docker/nri/conf.d
+1 -1
View File
@@ -1 +1 @@
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["foo="],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ServerErrors":["a server error occurred"],"ClientInfo":{"Debug":false,"Context":"","Plugins":[],"Warnings":null}}
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["foo="],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"NRI":{"Info":[["plugin-path","/usr/libexec/docker/nri-plugins"],["plugin-config-path","/etc/docker/nri/conf.d"]]},"Warnings":null,"ServerErrors":["a server error occurred"],"ClientInfo":{"Debug":false,"Context":"","Plugins":[],"Warnings":null}}
@@ -1 +1 @@
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":["WARNING: No memory limit support","WARNING: No swap limit support","WARNING: No oom kill disable support","WARNING: No cpu cfs quota support","WARNING: No cpu cfs period support","WARNING: No cpu shares support","WARNING: No cpuset support","WARNING: IPv4 forwarding is disabled"],"ClientInfo":{"Debug":true,"Platform":{"Name":"Docker Engine - Community"},"Version":"24.0.0","Context":"default","Plugins":[],"Warnings":null}}
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"NRI":{"Info":[["plugin-path","/usr/libexec/docker/nri-plugins"],["plugin-config-path","/etc/docker/nri/conf.d"]]},"Warnings":["WARNING: No memory limit support","WARNING: No swap limit support","WARNING: No oom kill disable support","WARNING: No cpu cfs quota support","WARNING: No cpu cfs period support","WARNING: No cpu shares support","WARNING: No cpuset support","WARNING: IPv4 forwarding is disabled"],"ClientInfo":{"Debug":true,"Platform":{"Name":"Docker Engine - Community"},"Version":"24.0.0","Context":"default","Plugins":[],"Warnings":null}}
@@ -59,4 +59,7 @@ Server:
Base: 10.123.0.0/16, Size: 24
Firewall Backend: nftables+firewalld
ReloadedAt: 2025-07-16T16:59:14Z
NRI:
plugin-path: /usr/libexec/docker/nri-plugins
plugin-config-path: /etc/docker/nri/conf.d
@@ -1 +1 @@
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ClientInfo":{"Debug":true,"Platform":{"Name":"Docker Engine - Community"},"Version":"24.0.0","Context":"default","Plugins":[],"Warnings":null}}
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"NRI":{"Info":[["plugin-path","/usr/libexec/docker/nri-plugins"],["plugin-config-path","/etc/docker/nri/conf.d"]]},"Warnings":null,"ClientInfo":{"Debug":true,"Platform":{"Name":"Docker Engine - Community"},"Version":"24.0.0","Context":"default","Plugins":[],"Warnings":null}}
@@ -64,4 +64,7 @@ Server:
Base: 10.123.0.0/16, Size: 24
Firewall Backend: nftables+firewalld
ReloadedAt: 2025-07-16T16:59:14Z
NRI:
plugin-path: /usr/libexec/docker/nri-plugins
plugin-config-path: /etc/docker/nri/conf.d
@@ -1 +1 @@
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","Version":"0.1.0","ShortDescription":"unit test is good","Name":"goodplugin","Path":"/path/to/docker-goodplugin"},{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","ShortDescription":"this plugin has no version","Name":"unversionedplugin","Path":"/path/to/docker-unversionedplugin"},{"Name":"badplugin","Path":"/path/to/docker-badplugin","Err":"something wrong"}],"Warnings":null}}
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"NRI":{"Info":[["plugin-path","/usr/libexec/docker/nri-plugins"],["plugin-config-path","/etc/docker/nri/conf.d"]]},"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","Version":"0.1.0","ShortDescription":"unit test is good","Name":"goodplugin","Path":"/path/to/docker-goodplugin"},{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","ShortDescription":"this plugin has no version","Name":"unversionedplugin","Path":"/path/to/docker-unversionedplugin"},{"Name":"badplugin","Path":"/path/to/docker-badplugin","Err":"something wrong"}],"Warnings":null}}
@@ -59,4 +59,7 @@ Server:
Base: 10.123.0.0/16, Size: 24
Firewall Backend: nftables+firewalld
ReloadedAt: 2025-07-16T16:59:14Z
NRI:
plugin-path: /usr/libexec/docker/nri-plugins
plugin-config-path: /etc/docker/nri/conf.d
@@ -1 +1 @@
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"DiscoveredDevices":[{"Source":"cdi","ID":"com.example.device1"},{"Source":"cdi","ID":"nvidia.com/gpu=gpu0"}],"Warnings":null}
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"DiscoveredDevices":[{"Source":"cdi","ID":"com.example.device1"},{"Source":"cdi","ID":"nvidia.com/gpu=gpu0"}],"NRI":{"Info":[["plugin-path","/usr/libexec/docker/nri-plugins"],["plugin-config-path","/etc/docker/nri/conf.d"]]},"Warnings":null}
@@ -56,4 +56,7 @@ Server:
Base: 10.123.0.0/16, Size: 24
Firewall Backend: nftables+firewalld
ReloadedAt: 2025-07-16T16:59:14Z
NRI:
plugin-path: /usr/libexec/docker/nri-plugins
plugin-config-path: /etc/docker/nri/conf.d
@@ -56,4 +56,7 @@ Server:
Base: 10.123.0.0/16, Size: 24
Firewall Backend: nftables+firewalld
ReloadedAt: 2025-07-16T16:59:14Z
NRI:
plugin-path: /usr/libexec/docker/nri-plugins
plugin-config-path: /etc/docker/nri/conf.d
@@ -80,4 +80,7 @@ Server:
Base: 10.123.0.0/16, Size: 24
Firewall Backend: nftables+firewalld
ReloadedAt: 2025-07-16T16:59:14Z
NRI:
plugin-path: /usr/libexec/docker/nri-plugins
plugin-config-path: /etc/docker/nri/conf.d
@@ -1 +1 @@
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"qo2dfdig9mmxqkawulggepdih","NodeAddr":"165.227.107.89","LocalNodeState":"active","ControlAvailable":true,"Error":"","RemoteManagers":[{"NodeID":"qo2dfdig9mmxqkawulggepdih","Addr":"165.227.107.89:2377"}],"Nodes":1,"Managers":1,"Cluster":{"ID":"9vs5ygs0gguyyec4iqf2314c0","Version":{"Index":11},"CreatedAt":"2017-08-24T17:34:19.278062352Z","UpdatedAt":"2017-08-24T17:34:42.398815481Z","Spec":{"Name":"default","Labels":null,"Orchestration":{"TaskHistoryRetentionLimit":5},"Raft":{"SnapshotInterval":10000,"KeepOldSnapshots":0,"LogEntriesForSlowFollowers":500,"ElectionTick":3,"HeartbeatTick":1},"Dispatcher":{"HeartbeatPeriod":5000000000},"CAConfig":{"NodeCertExpiry":7776000000000000},"TaskDefaults":{},"EncryptionConfig":{"AutoLockManagers":true}},"TLSInfo":{"TrustRoot":"\n-----BEGIN CERTIFICATE-----\nMIIBajCCARCgAwIBAgIUaFCW5xsq8eyiJ+Pmcv3MCflMLnMwCgYIKoZIzj0EAwIw\nEzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwODI0MTcyOTAwWhcNMzcwODE5MTcy\nOTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH\nA0IABDy7NebyUJyUjWJDBUdnZoV6GBxEGKO4TZPNDwnxDxJcUdLVaB7WGa4/DLrW\nUfsVgh1JGik2VTiLuTMA1tLlNPOjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB\nAf8EBTADAQH/MB0GA1UdDgQWBBQl16XFtaaXiUAwEuJptJlDjfKskDAKBggqhkjO\nPQQDAgNIADBFAiEAo9fTQNM5DP9bHVcTJYfl2Cay1bFu1E+lnpmN+EYJfeACIGKH\n1pCUkZ+D0IB6CiEZGWSHyLuXPM1rlP+I5KuS7sB8\n-----END CERTIFICATE-----\n","CertIssuerSubject":"MBMxETAPBgNVBAMTCHN3YXJtLWNh","CertIssuerPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLs15vJQnJSNYkMFR2dmhXoYHEQYo7hNk80PCfEPElxR0tVoHtYZrj8MutZR+xWCHUkaKTZVOIu5MwDW0uU08w=="},"RootRotationInProgress":false,"DefaultAddrPool":null,"SubnetSize":0,"DataPathPort":0}},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[],"Warnings":null}}
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"qo2dfdig9mmxqkawulggepdih","NodeAddr":"165.227.107.89","LocalNodeState":"active","ControlAvailable":true,"Error":"","RemoteManagers":[{"NodeID":"qo2dfdig9mmxqkawulggepdih","Addr":"165.227.107.89:2377"}],"Nodes":1,"Managers":1,"Cluster":{"ID":"9vs5ygs0gguyyec4iqf2314c0","Version":{"Index":11},"CreatedAt":"2017-08-24T17:34:19.278062352Z","UpdatedAt":"2017-08-24T17:34:42.398815481Z","Spec":{"Name":"default","Labels":null,"Orchestration":{"TaskHistoryRetentionLimit":5},"Raft":{"SnapshotInterval":10000,"KeepOldSnapshots":0,"LogEntriesForSlowFollowers":500,"ElectionTick":3,"HeartbeatTick":1},"Dispatcher":{"HeartbeatPeriod":5000000000},"CAConfig":{"NodeCertExpiry":7776000000000000},"TaskDefaults":{},"EncryptionConfig":{"AutoLockManagers":true}},"TLSInfo":{"TrustRoot":"\n-----BEGIN CERTIFICATE-----\nMIIBajCCARCgAwIBAgIUaFCW5xsq8eyiJ+Pmcv3MCflMLnMwCgYIKoZIzj0EAwIw\nEzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwODI0MTcyOTAwWhcNMzcwODE5MTcy\nOTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH\nA0IABDy7NebyUJyUjWJDBUdnZoV6GBxEGKO4TZPNDwnxDxJcUdLVaB7WGa4/DLrW\nUfsVgh1JGik2VTiLuTMA1tLlNPOjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB\nAf8EBTADAQH/MB0GA1UdDgQWBBQl16XFtaaXiUAwEuJptJlDjfKskDAKBggqhkjO\nPQQDAgNIADBFAiEAo9fTQNM5DP9bHVcTJYfl2Cay1bFu1E+lnpmN+EYJfeACIGKH\n1pCUkZ+D0IB6CiEZGWSHyLuXPM1rlP+I5KuS7sB8\n-----END CERTIFICATE-----\n","CertIssuerSubject":"MBMxETAPBgNVBAMTCHN3YXJtLWNh","CertIssuerPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLs15vJQnJSNYkMFR2dmhXoYHEQYo7hNk80PCfEPElxR0tVoHtYZrj8MutZR+xWCHUkaKTZVOIu5MwDW0uU08w=="},"RootRotationInProgress":false,"DefaultAddrPool":null,"SubnetSize":0,"DataPathPort":0}},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"NRI":{"Info":[["plugin-path","/usr/libexec/docker/nri-plugins"],["plugin-config-path","/etc/docker/nri/conf.d"]]},"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[],"Warnings":null}}
+3 -3
View File
@@ -155,11 +155,11 @@ func (e statusError) Error() string {
// Note: The root command's name is excluded. If cmd is the root cmd, return ""
func getCommandName(cmd *cobra.Command) string {
fullCmdName := getFullCommandName(cmd)
i := strings.Index(fullCmdName, " ")
if i == -1 {
_, after, ok := strings.Cut(fullCmdName, " ")
if !ok {
return ""
}
return fullCmdName[i+1:]
return after
}
// getFullCommandName gets the full cobra command name in the format
+4 -3
View File
@@ -4,14 +4,15 @@ import (
"bytes"
"context"
"io"
"reflect"
"strings"
"testing"
"github.com/docker/cli/cli/streams"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel/attribute"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func setupCobraCommands() (*cobra.Command, *cobra.Command, *cobra.Command) {
@@ -139,7 +140,7 @@ func TestStdioAttributes(t *testing.T) {
cli.Out().SetIsTerminal(tc.stdoutTty)
actual := stdioAttributes(cli)
assert.Check(t, reflect.DeepEqual(actual, tc.expected))
assert.Check(t, is.DeepEqual(actual, tc.expected, cmpopts.EquateComparable(attribute.Value{})))
})
}
}
@@ -179,7 +180,7 @@ func TestAttributesFromError(t *testing.T) {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
actual := attributesFromError(tc.err)
assert.Check(t, reflect.DeepEqual(actual, tc.expected))
assert.Check(t, is.DeepEqual(actual, tc.expected, cmpopts.EquateComparable(attribute.Value{})))
})
}
}
+5 -2
View File
@@ -1,3 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package volume
import (
@@ -166,7 +169,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, options createOptions
// each topology takes the form segment=value,segment=value
// comma-separated list of equal separated maps
segments := map[string]string{}
for _, segment := range strings.Split(top, ",") {
for segment := range strings.SplitSeq(top, ",") {
// TODO(dperny): validate topology syntax
k, v, _ := strings.Cut(segment, "=")
segments[k] = v
@@ -181,7 +184,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, options createOptions
// each topology takes the form segment=value,segment=value
// comma-separated list of equal separated maps
segments := map[string]string{}
for _, segment := range strings.Split(top, ",") {
for segment := range strings.SplitSeq(top, ",") {
// TODO(dperny): validate topology syntax
k, v, _ := strings.Cut(segment, "=")
segments[k] = v
+6 -3
View File
@@ -1,10 +1,13 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package volume
import (
"errors"
"fmt"
"io"
"reflect"
"maps"
"sort"
"strings"
"testing"
@@ -124,10 +127,10 @@ func TestVolumeCreateWithFlags(t *testing.T) {
if options.Driver != expectedDriver {
return client.VolumeCreateResult{}, fmt.Errorf("expected driver %q, got %q", expectedDriver, options.Driver)
}
if !reflect.DeepEqual(options.DriverOpts, expectedOpts) {
if !maps.Equal(options.DriverOpts, expectedOpts) {
return client.VolumeCreateResult{}, fmt.Errorf("expected drivers opts %v, got %v", expectedOpts, options.DriverOpts)
}
if !reflect.DeepEqual(options.Labels, expectedLabels) {
if !maps.Equal(options.Labels, expectedLabels) {
return client.VolumeCreateResult{}, fmt.Errorf("expected labels %v, got %v", expectedLabels, options.Labels)
}
return client.VolumeCreateResult{
+29 -29
View File
@@ -1,11 +1,16 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package convert
import (
"cmp"
"context"
"errors"
"fmt"
"net/netip"
"os"
"slices"
"sort"
"strings"
"time"
@@ -174,7 +179,11 @@ func Service(
}
func getPlacementPreference(preferences []composetypes.PlacementPreferences) []swarm.PlacementPreference {
result := []swarm.PlacementPreference{}
if len(preferences) == 0 {
return nil
}
result := make([]swarm.PlacementPreference, 0, len(preferences))
for _, preference := range preferences {
spreadDescriptor := preference.Spread
result = append(result, swarm.PlacementPreference{
@@ -198,7 +207,7 @@ func convertServiceNetworks(
}
}
nets := []swarm.NetworkAttachmentConfig{}
nets := make([]swarm.NetworkAttachmentConfig, 0, len(networks))
for networkName, nw := range networks {
networkConfig, ok := networkConfigs[networkName]
if !ok && networkName != defaultNetwork {
@@ -241,8 +250,6 @@ func convertServiceSecrets(
secrets []composetypes.ServiceSecretConfig,
secretSpecs map[string]composetypes.SecretConfig,
) ([]*swarm.SecretReference, error) {
refs := []*swarm.SecretReference{}
lookup := func(key string) (composetypes.FileObjectConfig, error) {
secretSpec, exists := secretSpecs[key]
if !exists {
@@ -250,6 +257,8 @@ func convertServiceSecrets(
}
return composetypes.FileObjectConfig(secretSpec), nil
}
refs := make([]*swarm.SecretReference, 0, len(secrets))
for _, secret := range secrets {
obj, err := convertFileObject(namespace, composetypes.FileReferenceConfig(secret), lookup)
if err != nil {
@@ -564,20 +573,17 @@ func convertResources(source composetypes.Resources) (*swarm.ResourceRequirement
}
func convertEndpointSpec(endpointMode string, source []composetypes.ServicePortConfig) *swarm.EndpointSpec {
portConfigs := []swarm.PortConfig{}
portConfigs := make([]swarm.PortConfig, 0, len(source))
for _, port := range source {
portConfig := swarm.PortConfig{
portConfigs = append(portConfigs, swarm.PortConfig{
Protocol: network.IPProtocol(port.Protocol),
TargetPort: port.Target,
PublishedPort: port.Published,
PublishMode: swarm.PortConfigPublishMode(port.Mode),
}
portConfigs = append(portConfigs, portConfig)
})
}
sort.Slice(portConfigs, func(i, j int) bool {
return portConfigs[i].PublishedPort < portConfigs[j].PublishedPort
})
slices.SortFunc(portConfigs, swarm.PortConfig.Compare)
return &swarm.EndpointSpec{
Mode: swarm.ResolutionMode(strings.ToLower(endpointMode)),
@@ -698,28 +704,22 @@ func convertCredentialSpec(namespace Namespace, spec composetypes.CredentialSpec
}
func convertUlimits(origUlimits map[string]*composetypes.UlimitsConfig) []*container.Ulimit {
newUlimits := make(map[string]*container.Ulimit)
ulimits := make([]*container.Ulimit, 0, len(origUlimits))
for name, u := range origUlimits {
soft, hard := int64(u.Soft), int64(u.Hard)
if u.Single != 0 {
newUlimits[name] = &container.Ulimit{
Name: name,
Soft: int64(u.Single),
Hard: int64(u.Single),
}
} else {
newUlimits[name] = &container.Ulimit{
Name: name,
Soft: int64(u.Soft),
Hard: int64(u.Hard),
}
soft, hard = int64(u.Single), int64(u.Single)
}
ulimits = append(ulimits, &container.Ulimit{
Name: name,
Soft: soft,
Hard: hard,
})
}
ulimits := make([]*container.Ulimit, 0, len(newUlimits))
for _, ulimit := range newUlimits {
ulimits = append(ulimits, ulimit)
}
sort.SliceStable(ulimits, func(i, j int) bool {
return ulimits[i].Name < ulimits[j].Name
slices.SortFunc(ulimits, func(a, b *container.Ulimit) int {
return cmp.Compare(a.Name, b.Name)
})
return ulimits
}
+22 -23
View File
@@ -6,6 +6,7 @@ package loader
import (
"errors"
"fmt"
"maps"
"path"
"path/filepath"
"reflect"
@@ -249,9 +250,7 @@ func GetDeprecatedProperties(configDicts ...map[string]any) map[string]string {
for _, configDict := range configDicts {
deprecatedProperties := getProperties(getServices(configDict), types.DeprecatedProperties)
for key, value := range deprecatedProperties {
deprecated[key] = value
}
maps.Copy(deprecated, deprecatedProperties)
}
return deprecated
@@ -322,26 +321,26 @@ type Transformer struct {
func createTransformHook(additionalTransformers ...Transformer) mapstructure.DecodeHookFuncType {
transforms := map[reflect.Type]func(any) (any, error){
reflect.TypeOf(types.External{}): transformExternal,
reflect.TypeOf(types.HealthCheckTest{}): transformHealthCheckTest,
reflect.TypeOf(types.ShellCommand{}): transformShellCommand,
reflect.TypeOf(types.StringList{}): transformStringList,
reflect.TypeOf(map[string]string{}): transformMapStringString,
reflect.TypeOf(types.UlimitsConfig{}): transformUlimits,
reflect.TypeOf(types.UnitBytes(0)): transformSize,
reflect.TypeOf([]types.ServicePortConfig{}): transformServicePort,
reflect.TypeOf(types.ServiceSecretConfig{}): transformStringSourceMap,
reflect.TypeOf(types.ServiceConfigObjConfig{}): transformStringSourceMap,
reflect.TypeOf(types.StringOrNumberList{}): transformStringOrNumberList,
reflect.TypeOf(map[string]*types.ServiceNetworkConfig{}): transformServiceNetworkMap,
reflect.TypeOf(types.Mapping{}): transformMappingOrListFunc("=", false),
reflect.TypeOf(types.MappingWithEquals{}): transformMappingOrListFunc("=", true),
reflect.TypeOf(types.Labels{}): transformMappingOrListFunc("=", false),
reflect.TypeOf(types.MappingWithColon{}): transformMappingOrListFunc(":", false),
reflect.TypeOf(types.HostsList{}): transformHostsList,
reflect.TypeOf(types.ServiceVolumeConfig{}): transformServiceVolumeConfig,
reflect.TypeOf(types.BuildConfig{}): transformBuildConfig,
reflect.TypeOf(types.Duration(0)): transformStringToDuration,
reflect.TypeFor[types.External](): transformExternal,
reflect.TypeFor[types.HealthCheckTest](): transformHealthCheckTest,
reflect.TypeFor[types.ShellCommand](): transformShellCommand,
reflect.TypeFor[types.StringList](): transformStringList,
reflect.TypeFor[map[string]string](): transformMapStringString,
reflect.TypeFor[types.UlimitsConfig](): transformUlimits,
reflect.TypeFor[types.UnitBytes](): transformSize,
reflect.TypeFor[[]types.ServicePortConfig](): transformServicePort,
reflect.TypeFor[types.ServiceSecretConfig](): transformStringSourceMap,
reflect.TypeFor[types.ServiceConfigObjConfig](): transformStringSourceMap,
reflect.TypeFor[types.StringOrNumberList](): transformStringOrNumberList,
reflect.TypeFor[map[string]*types.ServiceNetworkConfig](): transformServiceNetworkMap,
reflect.TypeFor[types.Mapping](): transformMappingOrListFunc("=", false),
reflect.TypeFor[types.MappingWithEquals](): transformMappingOrListFunc("=", true),
reflect.TypeFor[types.Labels](): transformMappingOrListFunc("=", false),
reflect.TypeFor[types.MappingWithColon](): transformMappingOrListFunc(":", false),
reflect.TypeFor[types.HostsList](): transformHostsList,
reflect.TypeFor[types.ServiceVolumeConfig](): transformServiceVolumeConfig,
reflect.TypeFor[types.BuildConfig](): transformBuildConfig,
reflect.TypeFor[types.Duration](): transformStringToDuration,
}
for _, transformer := range additionalTransformers {
+62 -78
View File
@@ -4,8 +4,11 @@
package loader
import (
"cmp"
"errors"
"fmt"
"reflect"
"slices"
"sort"
"dario.cat/mergo"
@@ -26,62 +29,73 @@ func (s *specials) Transformer(t reflect.Type) func(dst, src reflect.Value) erro
func merge(configs []*types.Config) (*types.Config, error) {
base := configs[0]
for _, override := range configs[1:] {
var err error
base.Services, err = mergeServices(base.Services, override.Services)
if err != nil {
return base, fmt.Errorf("cannot merge services from %s: %w", override.Filename, err)
var errs []error
if services, err := mergeServices(base.Services, override.Services); err != nil {
errs = append(errs, fmt.Errorf("cannot merge services: %w", err))
} else {
base.Services = services
}
base.Volumes, err = mergeVolumes(base.Volumes, override.Volumes)
if err != nil {
return base, fmt.Errorf("cannot merge volumes from %s: %w", override.Filename, err)
if err := mergo.Map(&base.Volumes, &override.Volumes, mergo.WithOverride); err != nil {
errs = append(errs, fmt.Errorf("cannot merge volumes: %w", err))
}
base.Networks, err = mergeNetworks(base.Networks, override.Networks)
if err != nil {
return base, fmt.Errorf("cannot merge networks from %s: %w", override.Filename, err)
if err := mergo.Map(&base.Networks, &override.Networks, mergo.WithOverride); err != nil {
errs = append(errs, fmt.Errorf("cannot merge networks: %w", err))
}
base.Secrets, err = mergeSecrets(base.Secrets, override.Secrets)
if err != nil {
return base, fmt.Errorf("cannot merge secrets from %s: %w", override.Filename, err)
if err := mergo.Map(&base.Secrets, &override.Secrets, mergo.WithOverride); err != nil {
errs = append(errs, fmt.Errorf("cannot merge secrets: %w", err))
}
base.Configs, err = mergeConfigs(base.Configs, override.Configs)
if err != nil {
return base, fmt.Errorf("cannot merge configs from %s: %w", override.Filename, err)
if err := mergo.Map(&base.Configs, &override.Configs, mergo.WithOverride); err != nil {
errs = append(errs, fmt.Errorf("cannot merge configs: %w", err))
}
if err := errors.Join(errs...); err != nil {
return nil, errors.Join(fmt.Errorf("failed to merge file %s", override.Filename), err)
}
}
return base, nil
}
func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, error) {
baseServices := mapByName(base)
overrideServices := mapByName(override)
specials := &specials{
m: map[reflect.Type]func(dst, src reflect.Value) error{
reflect.TypeOf(&types.LoggingConfig{}): safelyMerge(mergeLoggingConfig),
reflect.TypeOf([]types.ServicePortConfig{}): mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice),
reflect.TypeOf([]types.ServiceSecretConfig{}): mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice),
reflect.TypeOf([]types.ServiceConfigObjConfig{}): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice),
reflect.TypeOf(&types.UlimitsConfig{}): mergeUlimitsConfig,
reflect.TypeOf([]types.ServiceVolumeConfig{}): mergeSlice(toServiceVolumeConfigsMap, toServiceVolumeConfigsSlice),
reflect.TypeOf(types.ShellCommand{}): mergeShellCommand,
reflect.TypeOf(&types.ServiceNetworkConfig{}): mergeServiceNetworkConfig,
reflect.PointerTo(reflect.TypeOf(uint64(1))): mergeUint64,
},
mergeOpts := []func(*mergo.Config){
mergo.WithAppendSlice,
mergo.WithOverride,
mergo.WithTransformers(&specials{m: map[reflect.Type]func(dst, src reflect.Value) error{
reflect.PointerTo(reflect.TypeFor[types.LoggingConfig]()): safelyMerge(mergeLoggingConfig),
reflect.TypeFor[[]types.ServicePortConfig](): mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice),
reflect.TypeFor[[]types.ServiceSecretConfig](): mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice),
reflect.TypeFor[[]types.ServiceConfigObjConfig](): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice),
reflect.PointerTo(reflect.TypeFor[types.UlimitsConfig]()): mergeUlimitsConfig,
reflect.TypeFor[[]types.ServiceVolumeConfig](): mergeSlice(toServiceVolumeConfigsMap, toServiceVolumeConfigsSlice),
reflect.TypeFor[types.ShellCommand](): mergeShellCommand,
reflect.PointerTo(reflect.TypeFor[types.ServiceNetworkConfig]()): mergeServiceNetworkConfig,
reflect.PointerTo(reflect.TypeFor[uint64]()): mergeUint64,
}}),
}
for name, overrideService := range overrideServices {
if baseService, ok := baseServices[name]; ok {
if err := mergo.Merge(&baseService, &overrideService, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(specials)); err != nil {
return base, fmt.Errorf("cannot merge service %s: %w", name, err)
baseServices := make(map[string]types.ServiceConfig, len(base))
for _, s := range base {
baseServices[s.Name] = s
}
for _, overrideService := range override {
if baseService, ok := baseServices[overrideService.Name]; ok {
if err := mergo.Merge(&baseService, &overrideService, mergeOpts...); err != nil {
return nil, fmt.Errorf("cannot merge service %s: %w", overrideService.Name, err)
}
baseServices[name] = baseService
baseServices[overrideService.Name] = baseService
continue
}
baseServices[name] = overrideService
baseServices[overrideService.Name] = overrideService
}
services := []types.ServiceConfig{}
services := make([]types.ServiceConfig, 0, len(baseServices))
for _, baseService := range baseServices {
services = append(services, baseService)
}
sort.Slice(services, func(i, j int) bool { return services[i].Name < services[j].Name })
slices.SortFunc(services, func(a, b types.ServiceConfig) int {
return cmp.Compare(a.Name, b.Name)
})
return services, nil
}
@@ -134,7 +148,7 @@ func toServiceVolumeConfigsMap(s any) (map[any]any, error) {
}
func toServiceSecretConfigsSlice(dst reflect.Value, m map[any]any) error {
s := []types.ServiceSecretConfig{}
s := make([]types.ServiceSecretConfig, 0, len(m))
for _, v := range m {
s = append(s, v.(types.ServiceSecretConfig))
}
@@ -144,7 +158,7 @@ func toServiceSecretConfigsSlice(dst reflect.Value, m map[any]any) error {
}
func toSServiceConfigObjConfigsSlice(dst reflect.Value, m map[any]any) error {
s := []types.ServiceConfigObjConfig{}
s := make([]types.ServiceConfigObjConfig, 0, len(m))
for _, v := range m {
s = append(s, v.(types.ServiceConfigObjConfig))
}
@@ -154,7 +168,7 @@ func toSServiceConfigObjConfigsSlice(dst reflect.Value, m map[any]any) error {
}
func toServicePortConfigsSlice(dst reflect.Value, m map[any]any) error {
s := []types.ServicePortConfig{}
s := make([]types.ServicePortConfig, 0, len(m))
for _, v := range m {
s = append(s, v.(types.ServicePortConfig))
}
@@ -164,7 +178,7 @@ func toServicePortConfigsSlice(dst reflect.Value, m map[any]any) error {
}
func toServiceVolumeConfigsSlice(dst reflect.Value, m map[any]any) error {
s := []types.ServiceVolumeConfig{}
s := make([]types.ServiceVolumeConfig, 0, len(m))
for _, v := range m {
s = append(s, v.(types.ServiceVolumeConfig))
}
@@ -217,11 +231,13 @@ func sliceToMap(tomap tomapFn, v reflect.Value) (map[any]any, error) {
}
func mergeLoggingConfig(dst, src reflect.Value) error {
dstDriver := dst.Elem().FieldByName("Driver").String()
srcDriver := src.Elem().FieldByName("Driver").String()
// Same driver, merging options
if getLoggingDriver(dst.Elem()) == getLoggingDriver(src.Elem()) ||
getLoggingDriver(dst.Elem()) == "" || getLoggingDriver(src.Elem()) == "" {
if getLoggingDriver(dst.Elem()) == "" {
dst.Elem().FieldByName("Driver").SetString(getLoggingDriver(src.Elem()))
if dstDriver == srcDriver || dstDriver == "" || srcDriver == "" {
if dstDriver == "" {
dst.Elem().FieldByName("Driver").SetString(srcDriver)
}
dstOptions := dst.Elem().FieldByName("Options").Interface().(map[string]string)
srcOptions := src.Elem().FieldByName("Options").Interface().(map[string]string)
@@ -269,35 +285,3 @@ func mergeUint64(dst, src reflect.Value) error {
}
return nil
}
func getLoggingDriver(v reflect.Value) string {
return v.FieldByName("Driver").String()
}
func mapByName(services []types.ServiceConfig) map[string]types.ServiceConfig {
m := map[string]types.ServiceConfig{}
for _, service := range services {
m[service.Name] = service
}
return m
}
func mergeVolumes(base, override map[string]types.VolumeConfig) (map[string]types.VolumeConfig, error) {
err := mergo.Map(&base, &override, mergo.WithOverride)
return base, err
}
func mergeNetworks(base, override map[string]types.NetworkConfig) (map[string]types.NetworkConfig, error) {
err := mergo.Map(&base, &override, mergo.WithOverride)
return base, err
}
func mergeSecrets(base, override map[string]types.SecretConfig) (map[string]types.SecretConfig, error) {
err := mergo.Map(&base, &override, mergo.WithOverride)
return base, err
}
func mergeConfigs(base, override map[string]types.ConfigObjConfig) (map[string]types.ConfigObjConfig, error) {
err := mergo.Map(&base, &override, mergo.WithOverride)
return base, err
}
+2 -2
View File
@@ -1153,7 +1153,7 @@ func TestLoadMultipleServiceVolumes(t *testing.T) {
func TestMergeUlimitsConfig(t *testing.T) {
specials := &specials{
m: map[reflect.Type]func(dst, src reflect.Value) error{
reflect.TypeOf(&types.UlimitsConfig{}): mergeUlimitsConfig,
reflect.TypeFor[*types.UlimitsConfig](): mergeUlimitsConfig,
},
}
base := map[string]*types.UlimitsConfig{
@@ -1189,7 +1189,7 @@ func TestMergeUlimitsConfig(t *testing.T) {
func TestMergeServiceNetworkConfig(t *testing.T) {
specials := &specials{
m: map[reflect.Type]func(dst, src reflect.Value) error{
reflect.TypeOf(&types.ServiceNetworkConfig{}): mergeServiceNetworkConfig,
reflect.TypeFor[*types.ServiceNetworkConfig](): mergeServiceNetworkConfig,
},
}
base := map[string]*types.ServiceNetworkConfig{
+2 -3
View File
@@ -5,6 +5,7 @@ package template
import (
"fmt"
"maps"
"regexp"
"strings"
@@ -140,9 +141,7 @@ func recurseExtract(value any, pattern regexper) map[string]string {
case map[string]any:
for _, elem := range val {
submap := recurseExtract(elem, pattern)
for k, v := range submap {
m[k] = v
}
maps.Copy(m, submap)
}
case []any:
+4 -5
View File
@@ -6,6 +6,7 @@ package types
import (
"encoding/json"
"fmt"
"maps"
"strconv"
"time"
@@ -129,9 +130,7 @@ func (c Config) MarshalJSON() ([]byte, error) {
if len(c.Configs) > 0 {
m["configs"] = c.Configs
}
for k, v := range c.Extras {
m[k] = v
}
maps.Copy(m, c.Extras)
return json.Marshal(m)
}
@@ -352,7 +351,7 @@ func (u UnitBytes) MarshalYAML() (any, error) {
// MarshalJSON makes UnitBytes implement json.Marshaler
func (u UnitBytes) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%d"`, u)), nil
return fmt.Appendf(nil, `"%d"`, u), nil
}
// RestartPolicy the service restart policy
@@ -562,7 +561,7 @@ func (e External) MarshalJSON() ([]byte, error) {
if e.Name == "" {
return []byte(strconv.FormatBool(e.External)), nil
}
return []byte(fmt.Sprintf(`{"name": %q}`, e.Name)), nil
return fmt.Appendf(nil, `{"name": %q}`, e.Name), nil
}
// CredentialSpecConfig for credential spec on Windows
+5 -3
View File
@@ -1,3 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package configfile
import (
@@ -6,6 +9,7 @@ import (
"errors"
"fmt"
"io"
"maps"
"os"
"path/filepath"
"strings"
@@ -374,9 +378,7 @@ func getConfiguredCredentialStore(c *ConfigFile, registryHostname string) string
func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig, error) {
auths := make(map[string]types.AuthConfig)
addAll := func(from map[string]types.AuthConfig) {
for reg, ac := range from {
auths[reg] = ac
}
maps.Copy(auths, from)
}
defaultStore := configFile.GetCredentialsStore("")
+2 -2
View File
@@ -73,10 +73,10 @@ func (m *mockCommand) Output() ([]byte, error) {
return []byte("program failed"), errCommandExited
}
case "list":
return []byte(fmt.Sprintf(`{"%s": "%s", "%s": "%s"}`, validServerAddress, "foo", validServerAddress2, "<token>")), nil
return fmt.Appendf(nil, `{"%s": "%s", "%s": "%s"}`, validServerAddress, "foo", validServerAddress2, "<token>"), nil
}
return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errCommandExited
return fmt.Appendf(nil, "unknown argument %q with %q", m.arg, inS), errCommandExited
}
// Input sets the input to send to a remote credentials helper.
+6 -4
View File
@@ -1,3 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
// Package connhelper provides helpers for connecting to a remote daemon host with custom logic.
package connhelper
@@ -6,6 +9,7 @@ import (
"fmt"
"net"
"net/url"
"slices"
"strings"
"github.com/docker/cli/cli/connhelper/commandconn"
@@ -89,10 +93,8 @@ func addSSHTimeout(sshFlags []string) []string {
// disablePseudoTerminalAllocation disables pseudo-terminal allocation to
// prevent SSH from executing as a login shell
func disablePseudoTerminalAllocation(sshFlags []string) []string {
for _, flag := range sshFlags {
if flag == "-T" {
return sshFlags
}
if slices.Contains(sshFlags, "-T") {
return sshFlags
}
return append(sshFlags, "-T")
}
+3 -5
View File
@@ -1,7 +1,6 @@
package connhelper
import (
"reflect"
"testing"
"gotest.tools/v3/assert"
@@ -27,7 +26,8 @@ func TestSSHFlags(t *testing.T) {
}
for _, tc := range testCases {
assert.DeepEqual(t, addSSHTimeout(tc.in), tc.out)
result := addSSHTimeout(tc.in)
assert.DeepEqual(t, result, tc.out)
}
}
@@ -57,9 +57,7 @@ func TestDisablePseudoTerminalAllocation(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := disablePseudoTerminalAllocation(tc.sshFlags)
if !reflect.DeepEqual(result, tc.expected) {
t.Errorf("expected %v, got %v", tc.expected, result)
}
assert.DeepEqual(t, result, tc.expected)
})
}
}
+1 -1
View File
@@ -175,7 +175,7 @@ func quoteCommand(commandAndArgs ...string) (string, error) {
quotedCmd = a
continue
}
quotedCmd += " " + a //nolint:perfsprint // ignore "concat-loop"; no need to use a string-builder for this.
quotedCmd += " " + a
}
// each part is quoted appropriately, so now we'll have a full
// shell command to pass off to "ssh"
+7 -7
View File
@@ -17,7 +17,7 @@ require (
github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.10
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel v1.40.0
gotest.tools/v3 v3.5.2
)
@@ -57,19 +57,19 @@ require (
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.31.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
+16 -16
View File
@@ -213,8 +213,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -252,26 +252,26 @@ github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc h1:zkGwegkOW709y0oiAr
github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc/go.mod h1:FM4U1E3NzlNMRnSUTU3P1UdukWhYGifqEsjk9fn7BCk=
github.com/zmap/zlint/v3 v3.1.0 h1:WjVytZo79m/L1+/Mlphl09WBob6YTGljN5IGWZFpAv0=
github.com/zmap/zlint/v3 v3.1.0/go.mod h1:L7t8s3sEKkb0A2BxGy1IWrxt1ZATa1R4QfJZaQOD3zU=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -302,8 +302,8 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
+4 -8
View File
@@ -22,8 +22,7 @@ import (
var pluginFilename = "docker-buildx"
func TestBuildWithBuilder(t *testing.T) {
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
ctx := t.Context()
testcases := []struct {
name string
@@ -131,8 +130,7 @@ func (*fakeClient) Ping(context.Context, client.PingOptions) (client.PingResult,
}
func TestBuildkitDisabled(t *testing.T) {
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
ctx := t.Context()
t.Setenv("DOCKER_BUILDKIT", "0")
@@ -172,8 +170,7 @@ func TestBuildkitDisabled(t *testing.T) {
}
func TestBuilderBroken(t *testing.T) {
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
ctx := t.Context()
dir := fs.NewDir(t, t.Name(),
fs.WithFile(pluginFilename, `#!/bin/sh exit 1`, fs.WithMode(0o777)),
@@ -212,8 +209,7 @@ func TestBuilderBroken(t *testing.T) {
func TestBuilderBrokenEnforced(t *testing.T) {
t.Setenv("DOCKER_BUILDKIT", "1")
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
ctx := t.Context()
dir := fs.NewDir(t, t.Name(),
fs.WithFile(pluginFilename, `#!/bin/sh exit 1`, fs.WithMode(0o777)),
+26 -13
View File
@@ -1,3 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package main
import (
@@ -7,6 +10,7 @@ import (
"os"
"os/exec"
"os/signal"
"slices"
"strings"
"syscall"
@@ -115,6 +119,20 @@ func getExitCode(err error) int {
return 1
}
// cmdErrorMessage extracts an error message suitable for passing to plugin
// hooks. If the error's message is empty (e.g. a StatusError with only an
// exit code), it falls back to a generic message so that hooks can still
// detect that the command failed.
func cmdErrorMessage(err error) string {
if err == nil {
return ""
}
if msg := err.Error(); msg != "" {
return msg
}
return fmt.Sprintf("exited with code %d", getExitCode(err))
}
func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand {
var (
opts *cliflags.ClientOptions
@@ -410,7 +428,7 @@ func forceExitAfter3TerminationSignals(ctx context.Context, streams command.Stre
signal.Notify(sig, platformsignals.TerminationSignals...)
// once we have received a total of 3 signals we force exit the cli
for i := 0; i < 2; i++ {
for range 2 {
<-sig
}
_, _ = fmt.Fprint(streams.Err(), "\ngot 3 SIGTERM/SIGINTs, forcefully exiting\n")
@@ -476,10 +494,11 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error {
subCommand = ccmd
if err != nil || pluginmanager.IsPluginCommand(ccmd) {
err := tryPluginRun(ctx, dockerCli, cmd, args[0], envs)
if ccmd != nil && dockerCli.Out().IsTerminal() && dockerCli.HooksEnabled() && !errdefs.IsNotFound(err) {
errMessage := cmdErrorMessage(err)
pluginmanager.RunPluginHooks(ctx, dockerCli, cmd, ccmd, args, errMessage)
}
if err == nil {
if ccmd != nil && dockerCli.Out().IsTerminal() && dockerCli.HooksEnabled() {
pluginmanager.RunPluginHooks(ctx, dockerCli, cmd, ccmd, args)
}
return nil
}
if !errdefs.IsNotFound(err) {
@@ -503,11 +522,7 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error {
// If the command is being executed in an interactive terminal
// and hook are enabled, run the plugin hooks.
if subCommand != nil && dockerCli.Out().IsTerminal() && dockerCli.HooksEnabled() {
var errMessage string
if err != nil {
errMessage = err.Error()
}
pluginmanager.RunCLICommandHooks(ctx, dockerCli, cmd, subCommand, errMessage)
pluginmanager.RunCLICommandHooks(ctx, dockerCli, cmd, subCommand, cmdErrorMessage(err))
}
return err
@@ -617,10 +632,8 @@ func findCommand(cmd *cobra.Command, cmds []string) bool {
if cmd == nil {
return false
}
for _, c := range cmds {
if c == cmd.Name() {
return true
}
if slices.Contains(cmds, cmd.Name()) {
return true
}
return findCommand(cmd.Parent(), cmds)
}
+63 -2
View File
@@ -4,12 +4,14 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"syscall"
"testing"
"time"
dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/commands"
"github.com/docker/cli/cli/debug"
@@ -38,8 +40,7 @@ func TestDisableFlagsInUseLineIsSet(t *testing.T) {
func TestClientDebugEnabled(t *testing.T) {
defer debug.Disable()
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
ctx := t.Context()
cli, err := command.NewDockerCli(command.WithBaseContext(ctx))
assert.NilError(t, err)
@@ -127,6 +128,66 @@ func TestUserTerminatedError(t *testing.T) {
assert.Equal(t, getExitCode(context.Cause(notifyCtx)), 143)
}
func TestGetExitCode(t *testing.T) {
t.Run("nil error returns 0", func(t *testing.T) {
assert.Equal(t, getExitCode(nil), 0)
})
t.Run("generic error returns 1", func(t *testing.T) {
assert.Equal(t, getExitCode(errors.New("some failure")), 1)
})
t.Run("StatusError with code", func(t *testing.T) {
err := dockercli.StatusError{StatusCode: 42}
assert.Equal(t, getExitCode(err), 42)
})
t.Run("StatusError with zero code falls back to 1", func(t *testing.T) {
err := dockercli.StatusError{StatusCode: 0, Status: "something went wrong"}
assert.Equal(t, getExitCode(err), 1)
})
t.Run("wrapped StatusError", func(t *testing.T) {
err := fmt.Errorf("wrapper: %w", dockercli.StatusError{StatusCode: 99})
assert.Equal(t, getExitCode(err), 99)
})
t.Run("SIGINT returns 130", func(t *testing.T) {
err := errCtxSignalTerminated{signal: syscall.SIGINT}
assert.Equal(t, getExitCode(err), 130)
})
t.Run("SIGTERM returns 143", func(t *testing.T) {
err := errCtxSignalTerminated{signal: syscall.SIGTERM}
assert.Equal(t, getExitCode(err), 143)
})
}
func TestCmdErrorMessage(t *testing.T) {
t.Run("nil error returns empty string", func(t *testing.T) {
assert.Equal(t, cmdErrorMessage(nil), "")
})
t.Run("generic error returns error message", func(t *testing.T) {
assert.Equal(t, cmdErrorMessage(errors.New("something broke")), "something broke")
})
t.Run("StatusError with Status field", func(t *testing.T) {
err := dockercli.StatusError{Status: "build failed", StatusCode: 1}
assert.Equal(t, cmdErrorMessage(err), "build failed")
})
t.Run("StatusError with only exit code falls back to generic message", func(t *testing.T) {
err := dockercli.StatusError{StatusCode: 42}
assert.Equal(t, cmdErrorMessage(err), "exited with code 42")
})
t.Run("wrapped error preserves message", func(t *testing.T) {
err := fmt.Errorf("wrapper: %w", errors.New("inner failure"))
assert.Equal(t, cmdErrorMessage(err), "wrapper: inner failure")
})
}
func TestVisitAll(t *testing.T) {
root := &cobra.Command{Use: "root"}
sub1 := &cobra.Command{Use: "sub1"}
+1 -1
View File
@@ -1,5 +1,5 @@
variable "GO_VERSION" {
default = "1.25.5"
default = null
}
variable "VERSION" {
default = ""
+1 -1
View File
@@ -109,7 +109,7 @@ mddocs: build_docker_image ## generate markdown files from go source
.PHONY: yamldocs
yamldocs: build_docker_image ## generate documentation YAML files consumed by docs repo
$(DOCKER_RUN) -it $(DEV_DOCKER_IMAGE_NAME) make yamldocs
$(DOCKER_RUN) $(DEV_DOCKER_IMAGE_NAME) make yamldocs
.PHONY: test ## run unit and e2e tests
test: test-unit test-e2e
+1 -1
View File
@@ -2,7 +2,7 @@
# ALPINE_VERSION sets the version of the alpine base image to use.
# It must be a supported tag in the docker.io/library/alpine image repository.
ARG ALPINE_VERSION=3.22
ARG ALPINE_VERSION=3.23
FROM alpine:${ALPINE_VERSION} AS gen
RUN apk add --no-cache bash git
+4 -4
View File
@@ -1,16 +1,16 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.25.5
ARG GO_VERSION=1.25.8
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
# It must be a supported tag in the docker.io/library/alpine image repository
# that's also available as alpine image variant for the Golang version used.
ARG ALPINE_VERSION=3.22
ARG ALPINE_VERSION=3.23
# BUILDX_VERSION sets the version of buildx to install in the dev container.
# It must be a valid tag in the docker.io/docker/buildx-bin image repository
# on Docker Hub.
ARG BUILDX_VERSION=0.29.1
ARG BUILDX_VERSION=0.31.1
FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS golang
@@ -18,7 +18,7 @@ ENV GOTOOLCHAIN=local
ENV CGO_ENABLED=0
FROM golang AS gofumpt
ARG GOFUMPT_VERSION=v0.7.0
ARG GOFUMPT_VERSION=v0.9.2
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
--mount=type=tmpfs,target=/go/src/ \

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