Compare commits

..
282 Commits
Author SHA1 Message Date
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
Sebastiaan van StijnandGitHub 0e6fee6c52 Merge pull request #6698 from thaJeztah/inline_parseWindowsDevice
cli/command/container: inline parseWindowsDevice, and minor cleanups
2025-12-16 16:33:03 +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
Sebastiaan van StijnandGitHub 88be58884c Merge pull request #6709 from vvoland/img-list-all-doc
docs: Update --all flag description to clarify it shows dangling images
2025-12-16 12:58:57 +01:00
Paweł Gronowski f7ddc8a7d1 docs: Update --all flag description to clarify it shows dangling images
The --all flag description was misleading by only mentioning
intermediate images, when it actually also controls the visibility of
dangling (untagged) images.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-16 12:27:25 +01:00
Paweł GronowskiandGitHub 00e23cfdb7 Merge pull request #6706 from docker/dependabot/github_actions/actions/upload-artifact-6
build(deps): bump actions/upload-artifact from 5 to 6
2025-12-15 13:08:03 +00:00
dependabot[bot]andGitHub 4d7a8b0fd5 build(deps): bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 08:05:21 +00:00
Sebastiaan van StijnandGitHub f52814d454 Merge pull request #6705 from vvoland/list-fix
image/list: Fix `dangling=false` handling
2025-12-12 15:45:37 +01:00
Paweł Gronowski 0f03c31ab2 image/list: Fix dangling=false handling
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-12 15:36:40 +01:00
Paweł Gronowski 1e259062fc cli/tree: Remove unused all field
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-12 15:36:40 +01:00
Sebastiaan van StijnandGitHub 4d6fc331b9 Merge pull request #6704 from vvoland/list-fix
image: Fix dangling image detection with graphdrivers
2025-12-12 13:25:44 +01:00
Paweł Gronowski 09a46645a0 image/tree: Add golden test
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-12 12:39:26 +01:00
Paweł Gronowski 0d88411f1b image/tree: Remove --all flag check for untagged images in non-expanded view
This reverts part of the logic introduced in 207bf52c27 which
incorrectly gated untagged images behind the --all flag in non-expanded
view.

The original fix was addressing the wrong layer of the problem.

The actual issue was that dangling images were being incorrectly passed
to the tree code in the first place.

This was properly fixed in 67f5e3413 which corrected the dangling image
detection logic to properly filter them out before reaching the tree
display code.

Now that dangling images are correctly filtered upstream, untagged
images that reach the tree view should be displayed regardless of the
--all flag setting.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-12 11:22:47 +01:00
Paweł Gronowski b315983898 image/tree: Fix width calculation for untagged images
When calculating column widths for the tree view, untagged images
weren't being properly accounted for in the width calculation.

This caused layout issues when there were tagged images were shorter
than the `<untagged>` string.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-12 11:18:27 +01:00
Paweł Gronowski 150a25b9ff image/tree: Extract untagged image name to const
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-12 11:17:42 +01:00
Paweł Gronowski 67f5e3413b image: Fix dangling image detection with graphdrivers
The isDangling function was incorrectly identifying images as dangling
when they had no RepoTags but had valid RepoDigests.

This can occur when the graphdrivers are used instead of the containerd
image store.

An image should only be considered dangling if it has no RepoTags,
regardless of whether it has RepoDigests.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-12 11:16:01 +01:00
Sebastiaan van Stijn 2e3425fbd4 cli/command/container: use consistent casing for dockerCLI arg
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-12 09:30:46 +01:00
Sebastiaan van Stijn de098367d0 cli/command/container: inline parseWindowsDevice
It's not parsing anything, so we may as well inline it to be more
clear what's done.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-12 09:24:53 +01:00
Paweł GronowskiandGitHub d96b7869af Merge pull request #6702 from thaJeztah/bump_compress
vendor: github.com/klauspost/compress v1.18.2
2025-12-11 19:50:57 +00:00
Sebastiaan van Stijn 15de6ce8f7 vendor: github.com/klauspost/compress v1.18.2
full diff: https://github.com/klauspost/compress/compare/v1.18.0...v1.18.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-11 19:58:08 +01:00
Paweł GronowskiandGitHub 815be4418f Merge pull request #6701 from thaJeztah/bump_aec2
vendor: github.com/morikuni/aec v1.1.0
2025-12-11 18:14:44 +00:00
Sebastiaan van StijnandGitHub ca0fb174cf Merge pull request #6700 from thaJeztah/fix_validation
docker run, create: don't swallow connection errors during validate
2025-12-11 18:12:13 +01:00
Sebastiaan van Stijn 5c406f5ee4 vendor: github.com/morikuni/aec v1.1.0
full diff: https://github.com/morikuni/aec/compare/v1.0.0...v1.1.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-11 17:30:12 +01:00
Sebastiaan van Stijn a6335c4226 docker run, create: don't swallow connection errors during validate
Some validation steps done by `docker create` (and `docker run`) are platform-
specific, and need to know the daemon's OS.

To get this information, the CLI.ServerInfo() method was used, which
discards connection errors, resulting in an empty OS, which causes
validation to fail with an "unknown server OS" error message.

This patch changes it to use the Client.Ping so that we can error when
failing to connect.

We should look if we can reduce the platform-specific validation and parsing
on the client-side, but at least this change should produce a more useful
error.

Before this patch:

    DOCKER_HOST=tcp://example.invalid docker run -it --rm --device=/dev/dri alpine
    docker: unknown server OS:

    Run 'docker run --help' for more information

With this patch:

    DOCKER_HOST=tcp://example.invalid docker run -it --rm --device=/dev/dri alpine
    failed to connect to the docker API at tcp://example.invalid:2375: lookup example.invalid on 192.168.65.7:53: no such host

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-11 17:03:51 +01:00
Paweł GronowskiandGitHub 91d44d6caf Merge pull request #6697 from thaJeztah/migrate_yaml
vendor: github.com/spf13/cobra v1.10.2, migrate to go.yaml.in/yaml/v3
2025-12-09 15:07:34 +00:00
Sebastiaan van Stijn 49021ad987 vendor: github.com/spf13/cobra v1.10.2, migrate to go.yaml.in/yaml/v3
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-09 15:47:11 +01:00
Sebastiaan van StijnandGitHub 1a1a4fc478 Merge pull request #6685 from thaJeztah/less_nat
remove some uses of go-connections/nat package
2025-12-05 11:06:55 +01:00
Sebastiaan van Stijn 6f75c0c8e2 add TODOs for replacing nat.ParsePortSpecs
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-04 22:07:01 +01:00
Sebastiaan van Stijn 9c10a9c9ac opts/swarmopts: remove use of nat.ParsePortRange
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-04 22:06:57 +01:00
Paweł GronowskiandGitHub 65cf8762d3 Merge pull request #6692 from thaJeztah/rm_FakeStore
internal/test: remove unused FakeStore
2025-12-04 12:36:05 +00:00
Sebastiaan van Stijn 9dfe779abb internal/test: remove unused FakeStore
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-04 00:18:33 +01:00
Sebastiaan van StijnandGitHub dfa98d33ea Merge pull request #6690 from thaJeztah/compose_file_completion
add shell completion for "docker stack deploy --compose-file"
2025-12-03 11:47:52 +01:00
Sebastiaan van Stijn c81e05eed8 add shell completion for "docker stack deploy --compose-file"
With this patch:

    docker stack deploy -c<TAB>
    .codecov.yml       contrib/           e2e/               pkg/
    .git/              build/             debian/            experimental/
    ...

    docker stack deploy -c contrib/otel/<TAB>
    compose.yaml  otelcol.yaml  prom.yaml

Note that filtering for the file-extension only appears to be functional
on bash, but not (currently) working on other shells (at least not on Fish).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-03 11:38:04 +01:00
Sebastiaan van StijnandGitHub 890dcca877 Merge pull request #6688 from vvoland/update-go
update to go1.25.5
2025-12-02 19:58:46 +01:00
Paweł Gronowski d544885316 update to go1.25.5
These releases include 2 security fixes following the security policy:

- crypto/x509: excessive resource consumption in printing error string for host certificate validation

    Within HostnameError.Error(), when constructing an error string, there is no limit to the number of hosts that will be printed out.
    Furthermore, the error string is constructed by repeated string concatenation, leading to quadratic runtime.

    Therefore, a certificate provided by a malicious actor can result in excessive resource consumption.
    HostnameError.Error() now limits the number of hosts and utilizes strings.Builder when constructing an error string.

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

    This is CVE-2025-61729 and Go issue https://go.dev/issue/76445.

- crypto/x509: excluded subdomain constraint does not restrict wildcard SANs

    An excluded subdomain constraint in a certificate chain does not restrict the
    usage of wildcard SANs in the leaf certificate. For example a constraint that
    excludes the subdomain test.example.com does not prevent a leaf certificate from
    claiming the SAN *.example.com.

    This is CVE-2025-61727 and Go issue https://go.dev/issue/76442.

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

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-02 18:03:18 +01:00
Paweł GronowskiandGitHub c197aa70ee Merge pull request #6687 from thaJeztah/use_subtests
opts/swarmopts: use sub-tests
2025-12-01 13:08:30 +00:00
Sebastiaan van Stijn ba683d8df3 opts/swarmopts: use sub-tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-01 13:01:37 +01:00
Paweł GronowskiandGitHub 0aedba58c2 Merge pull request #6669 from vvoland/29-norc
gha/e2e: Switch to 29 from 29-rc
2025-11-28 12:26:43 +01:00
Paweł Gronowski dd2be022c0 gha/e2e: Switch to rc and 29 latest
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-28 12:22:06 +01:00
Paweł GronowskiandGitHub 360952c8d3 Merge pull request #6680 from thaJeztah/bump_modules
vendor: github.com/moby/moby/client v0.2.1
2025-11-27 17:36:37 +01:00
Sebastiaan van StijnandGitHub 8fc15eaf2c Merge pull request #6579 from dvdksn/doc-daemon-buildc-example
docs: update buildgc example config to use new buildkit v0.17 options
2025-11-27 17:35:32 +01:00
Sebastiaan van Stijn 1abfbf298c vendor: github.com/moby/moby/client v0.2.1
full diff: https://github.com/moby/moby/compare/client/v0.1.0...v0.2.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 17:25:03 +01:00
David Karlsson e0d30db115 docs: update buildgc example config to use new buildkit v0.17 options
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2025-11-27 16:24:42 +01:00
Paweł GronowskiandGitHub 5691ade75a Merge pull request #6682 from thaJeztah/bump_dct_deps
cmd/docker-trust: update dependencies
2025-11-27 15:38:49 +01:00
Paweł GronowskiandGitHub 848dcad809 Merge pull request #6681 from thaJeztah/bump_x_deps2
vendor: update various golang.org/x/xxx dependencies
2025-11-27 15:38:16 +01:00
Sebastiaan van Stijn 6a0099bc8a cmd/docker-trust: bump golang.org/x/crypto v0.45.0
Hello gophers,

We have tagged version v0.45.0 of golang.org/x/crypto in order to address two
security issues.

This version fixes a vulnerability in the golang.org/x/crypto/ssh package and a
vulnerability in the golang.org/x/crypto/ssh/agent package which could cause
programs to consume unbounded memory or panic respectively.

SSH servers parsing GSSAPI authentication requests don't validate the number of
mechanisms specified in the request, allowing an attacker to cause unbounded
memory consumption.

Thanks to Jakub Ciolek for reporting this issue.

This is CVE-2025-58181 and Go issue https://go.dev/issue/76363.

SSH Agent servers do not validate the size of messages when processing new
identity requests, which may cause the program to panic if the message is
malformed due to an out of bounds read.

Thanks to Jakub Ciolek for reporting this issue.

This is CVE-2025-47914 and Go issue https://go.dev/issue/76364.

Cheers, Go Security team

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 14:38:30 +01:00
Sebastiaan van Stijn c90166ffa6 cmd/docker-trust: update dependencies
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 14:37:10 +01:00
Sebastiaan van Stijn ac5e886124 vendor: golang.org/x/net v0.47.0
full diff: https://github.com/golang/net/compare/v0.46.0...v0.47.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 14:28:29 +01:00
Sebastiaan van Stijn 3ec414638c vendor: golang.org/x/term v0.37.0
full diff: https://github.com/golang/term/compare/v0.36.0...v0.37.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 14:27:21 +01:00
Sebastiaan van Stijn 616e93a0c2 vendor: golang.org/x/text v0.31.0
full diff: https://github.com/golang/text/compare/v0.30.0...v0.31.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 14:24:45 +01:00
Sebastiaan van Stijn 1202f8a642 vendor: golang.org/x/sync v0.18.0
full diff: https://github.com/golang/sync/compare/v0.17.0...v0.18.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 14:23:31 +01:00
Sebastiaan van Stijn b67055c963 vendor: golang.org/x/sys v0.38.0
- cpu: add HPDS, LOR, PAN detection for arm64
- cpu: also use MRS instruction in getmmfr1
- cpu: use MRS instruction to read arm64 system registers
- unix: add consts for ELF handling
- unix: add SetMemPolicy and its mode/flag values
- unix: add SizeofNhmsg and SizeofNexthopGrp
- windows: add iphlpapi routing functions

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 14:22:32 +01:00
Sebastiaan van StijnandGitHub eee3e3d015 Merge pull request #6671 from docker/dependabot/github_actions/actions/checkout-6
build(deps): bump actions/checkout from 5 to 6
2025-11-27 10:42:16 +01:00
Paweł GronowskiandGitHub 3247a5aae3 Merge pull request #6675 from vvoland/img-list-noellipsis
image/tree: Allow image names to overflow instead of truncating
2025-11-24 21:35:37 +00:00
Paweł Gronowski 4759615835 image/tree: Allow image names to overflow instead of truncating
Users were experiencing poor UX when image names were truncated in the
table output.

Instead of cutting off long image names with ellipsis, the names now
wrap to the next line to ensure full visibility.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-24 22:12:37 +01:00
dependabot[bot]andGitHub 3099d4716c build(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-21 08:04:55 +00:00
Paweł GronowskiandGitHub 511dad69d0 Merge pull request #6667 from thaJeztah/use_format
image ls: allow custom format in cli config
2025-11-20 16:31:07 +00:00
Paweł GronowskiandGitHub 11f24b8458 Merge pull request #6668 from robmry/builttime-format
docker version: restore top-level BuildTime to RFC3339Nano format
2025-11-20 16:21:40 +00:00
Sebastiaan van Stijn d84396d4eb image ls: allow custom format in cli config
Setting a custom format in the cli cofig should still be supported,
and not produce an error when specifying "--tree". Specifyihg both
"--tree" and "--format" still produces an error, but we could consider
allowing "json" format in a future update.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-20 16:20:33 +00:00
Rob Murray 6751cd1690 docker version: restore top-level BuildTime to RFC3339Nano
Introduced by bff56f0 (cli/command/system: define struct for
formatting version).

In the "docker info" result, the Engine component's BuildTime should
be in time.ANSIC format, but the top level BuildTime field should use
time.RFC3339Nano.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-20 15:57:24 +00:00
Sebastiaan van StijnandGitHub 8108357bcb Merge pull request #6662 from dvdksn/doc-update-http-proxy-link
chore: update link/linktext to dockerd proxy config
2025-11-17 11:19:50 +01:00
David Karlsson 3a842587f9 chore: update link/linktext to dockerd proxy config
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2025-11-17 11:00:04 +01:00
551 changed files with 38766 additions and 57641 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/"
+8 -8
View File
@@ -35,7 +35,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
-
name: Create matrix
id: platforms
@@ -63,7 +63,7 @@ jobs:
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
-
name: Build
uses: docker/bake-action@v6
@@ -88,7 +88,7 @@ jobs:
fi
-
name: Upload artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: ${{ env.ARTIFACT_NAME }}
path: /tmp/out/*
@@ -101,16 +101,16 @@ jobs:
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
uses: docker/login-action@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@v4
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
-
name: Docker meta
id: meta
@@ -143,7 +143,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
-
name: Create matrix
id: platforms
@@ -165,7 +165,7 @@ jobs:
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
-
name: Build
uses: docker/bake-action@v6
+2 -2
View File
@@ -46,7 +46,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 2
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
@@ -63,7 +63,7 @@ jobs:
name: Update Go
uses: actions/setup-go@v6
with:
go-version: "1.25.4"
go-version: "1.25.7"
-
name: Initialize CodeQL
uses: github/codeql-action/init@v4
+5 -5
View File
@@ -37,14 +37,14 @@ jobs:
- alpine
- debian
engine-version:
- 29-rc # latest rc
- 28 # latest
- 27 # latest - 1
- rc # latest rc
- 29 # latest
- 28 # latest - 1
- 25 # mirantis lts
steps:
-
name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@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@v4
-
name: Run ${{ matrix.target }}
run: |
+7 -4
View File
@@ -30,7 +30,7 @@ jobs:
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
-
name: Test
uses: docker/bake-action@v6
@@ -60,18 +60,21 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
path: ${{ env.GOPATH }}/src/github.com/docker/cli
-
name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.25.4"
go-version: "1.25.7"
-
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
+2 -2
View File
@@ -48,7 +48,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
-
name: Generate
shell: 'script --return --quiet --command "bash {0}"'
@@ -74,7 +74,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
-
name: Run
shell: 'script --return --quiet --command "bash {0}"'
+3 -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.4"
go: "1.25.7"
timeout: 5m
@@ -94,6 +94,8 @@ linters:
desc: Use github.com/google/uuid instead.
- pkg: "io/ioutil"
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
- pkg: "gopkg.in/yaml.v3"
desc: Use go.yaml.in/yaml/v3 instead.
forbidigo:
forbid:
+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.4
ARG GO_VERSION=1.25.7
# 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
}
}
+26 -2
View File
@@ -120,8 +120,8 @@ func TestNewAPIClientFromFlagsWithCustomHeadersFromEnv(t *testing.T) {
}
func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
const customVersion = "v3.3.3"
const expectedVersion = "3.3.3"
const customVersion = "v3.3"
const expectedVersion = "3.3"
t.Setenv("DOCKER_API_VERSION", customVersion)
t.Setenv("DOCKER_HOST", ":2375")
@@ -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"},
+4
View File
@@ -232,3 +232,7 @@ func (f *fakeClient) ContainerPause(ctx context.Context, containerID string, opt
return client.ContainerPauseResult{}, nil
}
func (*fakeClient) Ping(_ context.Context, _ client.PingOptions) (client.PingResult, error) {
return client.PingResult{}, nil
}
+44 -50
View File
@@ -95,14 +95,14 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
return cmd
}
func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error {
func runCreate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error {
if err := validatePullOpt(options.pull); err != nil {
return cli.StatusError{
Status: withHelp(err, "create").Error(),
StatusCode: 125,
}
}
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
proxyConfig := dockerCLI.ConfigFile().ParseProxyConfig(dockerCLI.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
newEnv := make([]string, 0, len(proxyConfig))
for k, v := range proxyConfig {
if v == nil {
@@ -112,24 +112,28 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
}
}
copts.env = *opts.NewListOptsRef(&newEnv, nil)
containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
serverInfo, err := dockerCLI.Client().Ping(ctx, client.PingOptions{})
if err != nil {
return err
}
containerCfg, err := parse(flags, copts, serverInfo.OSType)
if err != nil {
return cli.StatusError{
Status: withHelp(err, "create").Error(),
StatusCode: 125,
}
}
id, err := createContainer(ctx, dockerCli, containerCfg, options)
id, err := createContainer(ctx, dockerCLI, containerCfg, options)
if err != nil {
return err
}
_, _ = fmt.Fprintln(dockerCli.Out(), id)
_, _ = fmt.Fprintln(dockerCLI.Out(), id)
return nil
}
// FIXME(thaJeztah): this is the only code-path that uses APIClient.ImageCreate. Rewrite this to use the regular "pull" code (or vice-versa).
func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *createOptions) error {
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), img)
func pullImage(ctx context.Context, dockerCLI command.Cli, img string, options *createOptions) error {
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCLI.ConfigFile(), img)
if err != nil {
return err
}
@@ -139,7 +143,7 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
// Already validated.
ociPlatforms = append(ociPlatforms, platforms.MustParse(options.platform))
}
resp, err := dockerCli.Client().ImagePull(ctx, img, client.ImagePullOptions{
resp, err := dockerCLI.Client().ImagePull(ctx, img, client.ImagePullOptions{
RegistryAuth: encodedAuth,
Platforms: ociPlatforms,
})
@@ -150,7 +154,7 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
_ = resp.Close()
}()
out := dockerCli.Err()
out := dockerCLI.Err()
if options.quiet {
out = streams.NewOut(io.Discard)
}
@@ -172,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
@@ -183,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
@@ -207,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
@@ -216,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
}
}
@@ -243,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")
}
@@ -281,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)
}
@@ -314,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,
@@ -342,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,
@@ -366,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 {
@@ -423,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
}
}
+14 -16
View File
@@ -426,6 +426,9 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
return nil, err
}
// short syntax ([ip:]public:private[/proto])
//
// TODO(thaJeztah): we need an equivalent that handles the "ip-address" part without depending on the nat package.
ports, natPortBindings, err := nat.ParsePortSpecs(convertedOpts)
if err != nil {
return nil, err
@@ -792,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
}
}
@@ -899,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)
@@ -944,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()
}
}
}
@@ -994,9 +997,11 @@ func parseDevice(device, serverOS string) (container.DeviceMapping, error) {
case "linux":
return parseLinuxDevice(device)
case "windows":
return parseWindowsDevice(device)
// Windows doesn't support mapping, so passing the given value as-is.
return container.DeviceMapping{PathOnHost: device}, nil
default:
return container.DeviceMapping{}, fmt.Errorf("unknown server OS: %s", serverOS)
}
return container.DeviceMapping{}, fmt.Errorf("unknown server OS: %s", serverOS)
}
// parseLinuxDevice parses a device mapping string to a container.DeviceMapping struct
@@ -1027,18 +1032,11 @@ func parseLinuxDevice(device string) (container.DeviceMapping, error) {
dst = src
}
deviceMapping := container.DeviceMapping{
return container.DeviceMapping{
PathOnHost: src,
PathInContainer: dst,
CgroupPermissions: permissions,
}
return deviceMapping, nil
}
// parseWindowsDevice parses a device mapping string to a container.DeviceMapping struct
// knowing that the target is a Windows daemon
func parseWindowsDevice(device string) (container.DeviceMapping, error) {
return container.DeviceMapping{PathOnHost: device}, nil
}, nil
}
// validateDeviceCgroupRule validates a device cgroup rule string format
+17 -9
View File
@@ -84,14 +84,14 @@ func newRunCommand(dockerCLI command.Cli) *cobra.Command {
return cmd
}
func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
func runRun(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
if err := validatePullOpt(ropts.pull); err != nil {
return cli.StatusError{
Status: withHelp(err, "run").Error(),
StatusCode: 125,
}
}
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
proxyConfig := dockerCLI.ConfigFile().ParseProxyConfig(dockerCLI.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
newEnv := []string{}
for k, v := range proxyConfig {
if v == nil {
@@ -101,7 +101,12 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro
}
}
copts.env = *opts.NewListOptsRef(&newEnv, nil)
containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
serverInfo, err := dockerCLI.Client().Ping(ctx, client.PingOptions{})
if err != nil {
return err
}
containerCfg, err := parse(flags, copts, serverInfo.OSType)
// just in case the parse does not exit
if err != nil {
return cli.StatusError{
@@ -109,7 +114,7 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro
StatusCode: 125,
}
}
return runContainer(ctx, dockerCli, ropts, copts, containerCfg)
return runContainer(ctx, dockerCLI, ropts, copts, containerCfg)
}
//nolint:gocyclo
@@ -135,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)
@@ -167,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")
+1 -1
View File
@@ -60,7 +60,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)
+13 -13
View File
@@ -65,7 +65,7 @@ func newImagesCommand(dockerCLI command.Cli) *cobra.Command {
flags := cmd.Flags()
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only show image IDs")
flags.BoolVarP(&options.all, "all", "a", false, "Show all images (default hides intermediate images)")
flags.BoolVarP(&options.all, "all", "a", false, "Show all images (default hides intermediate and dangling images)")
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
flags.BoolVar(&options.showDigests, "digests", false, "Show digests")
flags.StringVar(&options.format, "format", "", flagsHelper.FormatHelp)
@@ -109,29 +109,29 @@ func runImages(ctx context.Context, dockerCLI command.Cli, options imagesOptions
images := res.Items
if !options.all {
if _, ok := filters["dangling"]; !ok {
if dangling, ok := filters["dangling"]; !ok || dangling["false"] {
images = slices.DeleteFunc(images, isDangling)
}
}
format := options.format
if len(format) == 0 {
if len(dockerCLI.ConfigFile().ImagesFormat) > 0 && !options.quiet && !options.tree {
format = dockerCLI.ConfigFile().ImagesFormat
useTree = false
} else {
format = formatter.TableFormatKey
}
}
if useTree {
return runTree(ctx, dockerCLI, treeOptions{
images: images,
all: options.all,
filters: filters,
expanded: options.tree,
})
}
format := options.format
if len(format) == 0 {
if len(dockerCLI.ConfigFile().ImagesFormat) > 0 && !options.quiet {
format = dockerCLI.ConfigFile().ImagesFormat
} else {
format = formatter.TableFormatKey
}
}
imageCtx := formatter.ImageContext{
Context: formatter.Context{
Output: dockerCLI.Out(),
@@ -176,7 +176,7 @@ func shouldUseTree(options imagesOptions) (bool, error) {
// isDangling is a copy of [formatter.isDangling].
func isDangling(img image.Summary) bool {
if len(img.RepoTags) == 0 && len(img.RepoDigests) == 0 {
if len(img.RepoTags) == 0 {
return true
}
return len(img.RepoTags) == 1 && img.RepoTags[0] == "<none>:<none>" && len(img.RepoDigests) == 1 && img.RepoDigests[0] == "<none>@<none>"
+82
View File
@@ -4,10 +4,12 @@ import (
"errors"
"fmt"
"io"
"slices"
"testing"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
@@ -116,6 +118,86 @@ func TestNewListCommandAmbiguous(t *testing.T) {
golden.Assert(t, cli.ErrBuffer().String(), "list-command-ambiguous.golden")
}
func TestImagesFilterDangling(t *testing.T) {
// Create test images with different states
items := []image.Summary{
{
ID: "sha256:87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7",
RepoTags: []string{"myimage:latest"},
RepoDigests: []string{"myimage@sha256:abc123"},
},
{
ID: "sha256:0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f",
RepoTags: []string{},
RepoDigests: []string{},
},
{
ID: "sha256:a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478",
RepoTags: []string{},
RepoDigests: []string{"image@sha256:a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478"},
},
}
testCases := []struct {
name string
args []string
imageListFunc func(options client.ImageListOptions) (client.ImageListResult, error)
}{
{
name: "dangling-true",
args: []string{"-f", "dangling=true"},
imageListFunc: func(options client.ImageListOptions) (client.ImageListResult, error) {
// Verify the filter is passed to the API
assert.Check(t, options.Filters["dangling"]["true"])
// dangling=true is handled on the server side and returns only dangling images
return client.ImageListResult{Items: []image.Summary{items[1], items[2]}}, nil
},
},
{
name: "dangling-false",
args: []string{"-f", "dangling=false"},
imageListFunc: func(options client.ImageListOptions) (client.ImageListResult, error) {
// Verify the filter is passed to the API
assert.Check(t, options.Filters["dangling"]["false"])
// Return all images including dangling
return client.ImageListResult{Items: slices.Clone(items)}, nil
},
},
{
name: "no-dangling-filter",
args: []string{},
imageListFunc: func(options client.ImageListOptions) (client.ImageListResult, error) {
// Verify no dangling filter is passed to the API
_, exists := options.Filters["dangling"]
assert.Check(t, !exists)
// Return all images including dangling
return client.ImageListResult{Items: slices.Clone(items)}, nil
},
},
{
name: "all-flag",
args: []string{"--all"},
imageListFunc: func(options client.ImageListOptions) (client.ImageListResult, error) {
// Verify the All flag is set
assert.Check(t, options.All)
// Return all images including dangling
return client.ImageListResult{Items: slices.Clone(items)}, nil
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc})
cmd := newImagesCommand(cli)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NilError(t, err)
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("list-command-filter-dangling.%s.golden", tc.name))
})
}
}
func nilToEmptySlice[T any](s []T) []T {
if s == nil {
return []T{}
@@ -0,0 +1,4 @@
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
myimage:latest 87428fc52280 0B 0B
<untagged> 0263829989b6 0B 0B
<untagged> a3a5e715f0cc 0B 0B
@@ -0,0 +1,2 @@
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
myimage:latest 87428fc52280 0B 0B
@@ -0,0 +1,3 @@
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
<untagged> 0263829989b6 0B 0B
<untagged> a3a5e715f0cc 0B 0B
@@ -0,0 +1,2 @@
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
myimage:latest 87428fc52280 0B 0B
@@ -1 +0,0 @@
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
@@ -0,0 +1,5 @@
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
multiplatform:latest aaaaaaaaaaaa 25.5 MB 20.2 MB U
├─ linux/amd64 bbbbbbbbbbbb 12.1 MB 10.0 MB
└─ linux/arm64 cccccccccccc 13.4 MB 10.2 MB U
@@ -0,0 +1,10 @@
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
app:v1
app:latest 101010101010 30.5 MB 25.2 MB U
└─ linux/amd64 202020202020 15.2 MB 12.6 MB U
<untagged> 303030303030 12.3 MB 10.1 MB
└─ linux/arm/v7 404040404040 6.1 MB 5.0 MB
base:alpine 505050505050 5.5 MB 5.5 MB
@@ -0,0 +1,5 @@
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
<untagged> dddddddddddd 18.5 MB 15.2 MB
├─ linux/amd64 eeeeeeeeeeee 9.2 MB 7.6 MB
└─ linux/arm64 ffffffffffff 9.3 MB 7.6 MB
@@ -0,0 +1,4 @@
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
a:1 111111111111 5.5 MB 2.5 MB
<untagged> 222222222222 3.2 MB 1.6 MB
short:v1 333333333333 7.1 MB 3.5 MB U
+63 -28
View File
@@ -22,9 +22,10 @@ import (
"github.com/opencontainers/go-digest"
)
const untaggedName = "<untagged>"
type treeOptions struct {
images []imagetypes.Summary
all bool
filters client.Filters
expanded bool
}
@@ -111,7 +112,7 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) (int,
continue
}
if opts.all && len(sortedTags) == 0 {
if len(sortedTags) == 0 {
view.images = append(view.images, topImage{
Details: topDetails,
Children: children,
@@ -258,9 +259,10 @@ func printImageTree(outs command.Streams, view treeView) {
possibleChips := getPossibleChips(view)
columns := []imgColumn{
{
Title: "Image",
Align: alignLeft,
Width: 0,
Title: "Image",
Align: alignLeft,
Width: 0,
NoEllipsis: true,
},
{
Title: "ID",
@@ -387,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
}
@@ -432,17 +431,31 @@ func printChildren(out tui.Output, headers []imgColumn, img topImage, normalColo
func printNames(out tui.Output, headers []imgColumn, img topImage, color, untaggedColor aec.ANSI) {
if len(img.Names) == 0 {
_, _ = fmt.Fprint(out, headers[0].Print(untaggedColor, "<untagged>"))
_, _ = fmt.Fprint(out, headers[0].Print(untaggedColor, untaggedName))
}
for nameIdx, name := range img.Names {
// Don't limit first names to the column width because only the last
// name will be printed alongside other columns.
if nameIdx < len(img.Names)-1 {
_, fullWidth := out.GetTtySize()
_, _ = fmt.Fprintln(out, color.Apply(tui.Ellipsis(name, int(fullWidth))))
} else {
_, _ = fmt.Fprint(out, headers[0].Print(color, name))
nameWidth := tui.Width(name)
lastName := nameIdx == len(img.Names)-1
multiLine := nameWidth > headers[0].Width
_, _ = fmt.Fprint(out, headers[0].Print(color, name))
// Print each name on its own line, including the last,
// unless the last name fits into the column.
//
// IMAGE ID ...
// anImage 171e65262c80 ...
// firstName
// lastNameIsALongOne
// eade5be814e8 ...
// anotherLongName
// bb747ca923a5 ...
if !lastName || multiLine {
_, _ = fmt.Fprintln(out)
}
if multiLine && lastName {
_, _ = fmt.Fprint(out, strings.Repeat(" ", headers[0].Width))
}
}
}
@@ -462,6 +475,7 @@ type imgColumn struct {
DetailsValue func(*imageDetails) string
Color *aec.ANSI
NoEllipsis bool
}
func (h imgColumn) Print(clr aec.ANSI, s string) string {
@@ -478,12 +492,16 @@ func (h imgColumn) Print(clr aec.ANSI, s string) string {
func (h imgColumn) PrintC(clr aec.ANSI, s string) string {
ln := tui.Width(s)
if ln > h.Width {
return clr.Apply(tui.Ellipsis(s, h.Width))
}
fill := h.Width - ln
if fill < 0 {
if h.NoEllipsis {
fill = 0
} else {
return clr.Apply(tui.Ellipsis(s, h.Width))
}
}
l := fill / 2
r := fill - l
@@ -492,27 +510,44 @@ func (h imgColumn) PrintC(clr aec.ANSI, s string) string {
func (h imgColumn) PrintL(clr aec.ANSI, s string) string {
ln := tui.Width(s)
if ln > h.Width {
return clr.Apply(tui.Ellipsis(s, h.Width))
fill := h.Width - ln
if fill < 0 {
if h.NoEllipsis {
fill = 0
} else {
return clr.Apply(tui.Ellipsis(s, h.Width))
}
}
return clr.Apply(s) + strings.Repeat(" ", h.Width-ln)
return clr.Apply(s) + strings.Repeat(" ", fill)
}
func (h imgColumn) PrintR(clr aec.ANSI, s string) string {
ln := tui.Width(s)
if ln > h.Width {
return clr.Apply(tui.Ellipsis(s, h.Width))
fill := h.Width - ln
if fill < 0 {
if h.NoEllipsis {
fill = 0
} else {
return clr.Apply(tui.Ellipsis(s, h.Width))
}
}
return strings.Repeat(" ", h.Width-ln) + clr.Apply(s)
return strings.Repeat(" ", fill) + clr.Apply(s)
}
// widestFirstColumnValue calculates the width needed to fully display the image names and platforms.
func widestFirstColumnValue(headers []imgColumn, images []topImage) int {
width := len(headers[0].Title)
for _, img := range images {
for _, name := range img.Names {
names := img.Names
if len(names) == 0 {
names = []string{untaggedName}
}
for _, name := range names {
if len(name) > width {
width = len(name)
}
+199
View File
@@ -1,11 +1,13 @@
package image
import (
"fmt"
"strings"
"testing"
"github.com/docker/cli/internal/test"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
)
func TestPrintImageTreeAnsiTty(t *testing.T) {
@@ -154,3 +156,200 @@ func TestPrintImageTreeAnsiTty(t *testing.T) {
})
}
}
func TestPrintImageTreeGolden(t *testing.T) {
testCases := []struct {
name string
view treeView
expanded bool
}{
{
name: "width-calculation-untagged",
expanded: false,
view: treeView{
images: []topImage{
{
Names: []string{"a:1"},
Details: imageDetails{
ID: "sha256:1111111111111111111111111111111111111111111111111111111111111111",
DiskUsage: "5.5 MB",
InUse: false,
ContentSize: "2.5 MB",
},
},
{
// Untagged image name is longer than "a:1"
Names: []string{},
Details: imageDetails{
ID: "sha256:2222222222222222222222222222222222222222222222222222222222222222",
DiskUsage: "3.2 MB",
InUse: false,
ContentSize: "1.6 MB",
},
},
{
Names: []string{"short:v1"},
Details: imageDetails{
ID: "sha256:3333333333333333333333333333333333333333333333333333333333333333",
DiskUsage: "7.1 MB",
InUse: true,
ContentSize: "3.5 MB",
},
},
},
imageSpacing: false,
},
},
{
name: "expanded-view-with-platforms",
expanded: false,
view: treeView{
images: []topImage{
{
Names: []string{"multiplatform:latest"},
Details: imageDetails{
ID: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
DiskUsage: "25.5 MB",
InUse: true,
ContentSize: "20.2 MB",
},
Children: []subImage{
{
Platform: "linux/amd64",
Available: true,
Details: imageDetails{
ID: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
DiskUsage: "12.1 MB",
InUse: false,
ContentSize: "10.0 MB",
},
},
{
Platform: "linux/arm64",
Available: true,
Details: imageDetails{
ID: "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
DiskUsage: "13.4 MB",
InUse: true,
ContentSize: "10.2 MB",
},
},
},
},
},
imageSpacing: true,
},
},
{
name: "untagged-with-platforms",
expanded: false,
view: treeView{
images: []topImage{
{
Names: []string{},
Details: imageDetails{
ID: "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
DiskUsage: "18.5 MB",
InUse: false,
ContentSize: "15.2 MB",
},
Children: []subImage{
{
Platform: "linux/amd64",
Available: true,
Details: imageDetails{
ID: "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
DiskUsage: "9.2 MB",
InUse: false,
ContentSize: "7.6 MB",
},
},
{
Platform: "linux/arm64",
Available: false,
Details: imageDetails{
ID: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
DiskUsage: "9.3 MB",
InUse: false,
ContentSize: "7.6 MB",
},
},
},
},
},
imageSpacing: true,
},
},
{
name: "mixed-tagged-untagged-with-children",
expanded: false,
view: treeView{
images: []topImage{
{
Names: []string{"app:v1", "app:latest"},
Details: imageDetails{
ID: "sha256:1010101010101010101010101010101010101010101010101010101010101010",
DiskUsage: "30.5 MB",
InUse: true,
ContentSize: "25.2 MB",
},
Children: []subImage{
{
Platform: "linux/amd64",
Available: true,
Details: imageDetails{
ID: "sha256:2020202020202020202020202020202020202020202020202020202020202020",
DiskUsage: "15.2 MB",
InUse: true,
ContentSize: "12.6 MB",
},
},
},
},
{
Names: []string{},
Details: imageDetails{
ID: "sha256:3030303030303030303030303030303030303030303030303030303030303030",
DiskUsage: "12.3 MB",
InUse: false,
ContentSize: "10.1 MB",
},
Children: []subImage{
{
Platform: "linux/arm/v7",
Available: true,
Details: imageDetails{
ID: "sha256:4040404040404040404040404040404040404040404040404040404040404040",
DiskUsage: "6.1 MB",
InUse: false,
ContentSize: "5.0 MB",
},
},
},
},
{
Names: []string{"base:alpine"},
Details: imageDetails{
ID: "sha256:5050505050505050505050505050505050505050505050505050505050505050",
DiskUsage: "5.5 MB",
InUse: false,
ContentSize: "5.5 MB",
},
},
},
imageSpacing: true,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(nil)
cli.Out().SetIsTerminal(false)
printImageTree(cli, tc.view)
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("tree-command-success.%s.golden", tc.name))
})
}
}
+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")
+1 -4
View File
@@ -662,10 +662,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))
}
})
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
composeLoader "github.com/docker/cli/cli/compose/loader"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
"go.yaml.in/yaml/v3"
)
// configOptions holds docker stack config options
+6 -1
View File
@@ -51,7 +51,12 @@ func newDeployCommand(dockerCLI command.Cli) *cobra.Command {
flags := cmd.Flags()
flags.StringSliceVarP(&opts.composefiles, "compose-file", "c", []string{}, `Path to a Compose file, or "-" to read from stdin`)
flags.SetAnnotation("compose-file", "version", []string{"1.25"})
_ = flags.SetAnnotation("compose-file", "version", []string{"1.25"})
// Provide tab-completion for filenames. On Bash, this is constrained to the
// ".yaml" and ".yml" file-extensions, but this doesn't appear to be supported
// by other shells.
_ = cmd.MarkFlagFilename("compose-file", "yaml", "yml")
flags.BoolVar(&opts.sendRegistryAuth, "with-registry-auth", false, "Send registry authentication details to Swarm agents")
flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced")
flags.SetAnnotation("prune", "version", []string{"1.27"})
+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"},
}
+1 -1
View File
@@ -1 +1 @@
{"Client":{"Version":"18.99.5-ce","ApiVersion":"1.38","DefaultAPIVersion":"1.38","GitCommit":"deadbeef","GoVersion":"go1.10.2","Os":"linux","Arch":"amd64","BuildTime":"Wed May 30 22:21:05 2018","Context":"my-context"},"Server":{"Platform":{"Name":"Docker Enterprise Edition (EE) 2.0"},"Version":"18.99.5-ce","ApiVersion":"1.30","MinAPIVersion":"1.12","Os":"linux","Arch":"amd64","Components":[{"Name":"Engine","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 9 23:38:38 2018","Experimental":"false","GitCommit":"64ddfa6","GoVersion":"go1.8.7","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"Universal Control Plane","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 2 21:24:07 UTC 2018","GitCommit":"4513922","GoVersion":"go1.9.4","MinApiVersion":"1.20","Os":"linux","Version":"3.0.3-tp2"}},{"Name":"Kubernetes","Version":"1.8+","Details":{"buildDate":"2018-04-26T16:51:21Z","compiler":"gc","gitCommit":"8d637aedf46b9c21dde723e29c645b9f27106fa5","gitTreeState":"clean","gitVersion":"v1.8.11-docker-8d637ae","goVersion":"go1.8.3","major":"1","minor":"8+","platform":"linux/amd64"}},{"Name":"Calico","Version":"v3.0.8","Details":{"cni":"v2.0.6","kube-controllers":"v2.0.5","node":"v3.0.8"}}],"GitCommit":"64ddfa6","GoVersion":"go1.8.7","KernelVersion":"v1.0.0","BuildTime":"Mon Jul 9 23:38:38 2018"}}
{"Client":{"Version":"18.99.5-ce","ApiVersion":"1.38","DefaultAPIVersion":"1.38","GitCommit":"deadbeef","GoVersion":"go1.10.2","Os":"linux","Arch":"amd64","BuildTime":"Wed May 30 22:21:05 2018","Context":"my-context"},"Server":{"Platform":{"Name":"Docker Enterprise Edition (EE) 2.0"},"Version":"18.99.5-ce","ApiVersion":"1.30","MinAPIVersion":"1.12","Os":"linux","Arch":"amd64","Components":[{"Name":"Engine","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 9 23:38:38 2018","Experimental":"false","GitCommit":"64ddfa6","GoVersion":"go1.8.7","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"Universal Control Plane","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 2 21:24:07 UTC 2018","GitCommit":"4513922","GoVersion":"go1.9.4","MinApiVersion":"1.20","Os":"linux","Version":"3.0.3-tp2"}},{"Name":"Kubernetes","Version":"1.8+","Details":{"buildDate":"2018-04-26T16:51:21Z","compiler":"gc","gitCommit":"8d637aedf46b9c21dde723e29c645b9f27106fa5","gitTreeState":"clean","gitVersion":"v1.8.11-docker-8d637ae","goVersion":"go1.8.3","major":"1","minor":"8+","platform":"linux/amd64"}},{"Name":"Calico","Version":"v3.0.8","Details":{"cni":"v2.0.6","kube-controllers":"v2.0.5","node":"v3.0.8"}}],"GitCommit":"64ddfa6","GoVersion":"go1.8.7","KernelVersion":"v1.0.0","BuildTime":"2018-07-09T22:38:38.000000000+00:00"}}
@@ -1 +1 @@
{"Client":{"Version":"18.99.5-ce","ApiVersion":"1.38","DefaultAPIVersion":"1.38","GitCommit":"deadbeef","GoVersion":"go1.10.2","Os":"linux","Arch":"amd64","BuildTime":"Wed May 30 22:21:05 2018","Context":"my-context"},"Server":{"Platform":{"Name":"Docker Enterprise Edition (EE) 2.0"},"Version":"18.99.5-ce","ApiVersion":"1.30","MinAPIVersion":"1.12","Os":"linux","Arch":"amd64","Components":[{"Name":"Engine","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 9 23:38:38 2018","Experimental":"false","GitCommit":"64ddfa6","GoVersion":"go1.8.7","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"Universal Control Plane","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 2 21:24:07 UTC 2018","GitCommit":"4513922","GoVersion":"go1.9.4","MinApiVersion":"1.20","Os":"linux","Version":"3.0.3-tp2"}},{"Name":"Kubernetes","Version":"1.8+","Details":{"buildDate":"2018-04-26T16:51:21Z","compiler":"gc","gitCommit":"8d637aedf46b9c21dde723e29c645b9f27106fa5","gitTreeState":"clean","gitVersion":"v1.8.11-docker-8d637ae","goVersion":"go1.8.3","major":"1","minor":"8+","platform":"linux/amd64"}},{"Name":"Calico","Version":"v3.0.8","Details":{"cni":"v2.0.6","kube-controllers":"v2.0.5","node":"v3.0.8"}}],"GitCommit":"64ddfa6","GoVersion":"go1.8.7","KernelVersion":"v1.0.0","BuildTime":"Mon Jul 9 23:38:38 2018"}}
{"Client":{"Version":"18.99.5-ce","ApiVersion":"1.38","DefaultAPIVersion":"1.38","GitCommit":"deadbeef","GoVersion":"go1.10.2","Os":"linux","Arch":"amd64","BuildTime":"Wed May 30 22:21:05 2018","Context":"my-context"},"Server":{"Platform":{"Name":"Docker Enterprise Edition (EE) 2.0"},"Version":"18.99.5-ce","ApiVersion":"1.30","MinAPIVersion":"1.12","Os":"linux","Arch":"amd64","Components":[{"Name":"Engine","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 9 23:38:38 2018","Experimental":"false","GitCommit":"64ddfa6","GoVersion":"go1.8.7","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"Universal Control Plane","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 2 21:24:07 UTC 2018","GitCommit":"4513922","GoVersion":"go1.9.4","MinApiVersion":"1.20","Os":"linux","Version":"3.0.3-tp2"}},{"Name":"Kubernetes","Version":"1.8+","Details":{"buildDate":"2018-04-26T16:51:21Z","compiler":"gc","gitCommit":"8d637aedf46b9c21dde723e29c645b9f27106fa5","gitTreeState":"clean","gitVersion":"v1.8.11-docker-8d637ae","goVersion":"go1.8.3","major":"1","minor":"8+","platform":"linux/amd64"}},{"Name":"Calico","Version":"v3.0.8","Details":{"cni":"v2.0.6","kube-controllers":"v2.0.5","node":"v3.0.8"}}],"GitCommit":"64ddfa6","GoVersion":"go1.8.7","KernelVersion":"v1.0.0","BuildTime":"2018-07-09T22:38:38.000000000+00:00"}}
+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}}
+1 -1
View File
@@ -151,7 +151,7 @@ func newServerVersion(sv client.ServerVersionResult) *serverVersion {
out.GoVersion = component.Details["GoVersion"]
out.KernelVersion = component.Details["KernelVersion"]
out.Experimental = func() bool { b, _ := strconv.ParseBool(component.Details["Experimental"]); return b }()
out.BuildTime = reformatDate(component.Details["BuildTime"])
out.BuildTime = buildTime
}
out.Components = append(out.Components, component)
}
+1 -1
View File
@@ -58,7 +58,7 @@ func TestVersionFormat(t *testing.T) {
GoVersion: "go1.8.7",
KernelVersion: "v1.0.0",
Experimental: false,
BuildTime: "Mon Jul 9 23:38:38 2018",
BuildTime: "2018-07-09T22:38:38.000000000+00:00",
Components: []system.ComponentVersion{
{
Name: "Engine",
+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
}
+27 -26
View File
@@ -6,6 +6,7 @@ package loader
import (
"errors"
"fmt"
"maps"
"path"
"path/filepath"
"reflect"
@@ -28,7 +29,7 @@ import (
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client/pkg/versions"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
"go.yaml.in/yaml/v3"
)
// Options supported by Load
@@ -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 {
@@ -918,8 +917,9 @@ var transformStringToDuration TransformerFunc = func(value any) (any, error) {
}
func toServicePortConfigs(value string) ([]any, error) {
var portConfigs []any
// short syntax ([ip:]public:private[/proto])
//
// TODO(thaJeztah): we need an equivalent that handles the "ip-address" part without depending on the nat package.
ports, portBindings, err := nat.ParsePortSpecs([]string{value})
if err != nil {
return nil, err
@@ -931,6 +931,7 @@ func toServicePortConfigs(value string) ([]any, error) {
}
sort.Strings(keys)
var portConfigs []any
for _, key := range keys {
// Reuse ConvertPortToPortConfig so that it is consistent
port, err := network.ParsePort(key)

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