Compare commits

..
179 Commits
Author SHA1 Message Date
Paweł GronowskiandGitHub 055a478ea9 Merge pull request #6945 from thaJeztah/bump_moby
vendor: github.com/moby/moby/client v0.4.1, moby/api v1.54.2
2026-04-20 16:57:44 +02:00
Sebastiaan van Stijn d0f5b279e9 cmd/docker-trust: bump moby/client v0.4.1, moby/api v1.54.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 16:50:53 +02:00
Sebastiaan van Stijn b7f37e86da vendor: github.com/moby/moby/client v0.4.1, moby/api v1.54.2
- https://github.com/moby/moby/compare/api/v1.54.1...api/v1.54.2
- https://github.com/moby/moby/compare/client/v0.4.0...client/v0.4.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 16:49:58 +02:00
Paweł GronowskiandGitHub c93d892f0e Merge pull request #6949 from thaJeztah/bump_utils
Dockerfile: update buildx to v0.33.0, compose v5.1.3
2026-04-20 16:26:38 +02:00
Paweł GronowskiandGitHub 3553cafa13 Merge pull request #6948 from thaJeztah/bump_trust_deps
cmd/docker-trust: bump dependencies
2026-04-20 16:26:17 +02:00
Sebastiaan van Stijn 266f039bb5 Dockerfile: update compose to v5.1.3
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 16:13:56 +02:00
Sebastiaan van Stijn d74d3c3b16 Dockerfile: update buildx to v0.33.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 16:12:56 +02:00
Sebastiaan van StijnandGitHub 134c2a0ed6 Merge pull request #6826 from thaJeztah/bump_golangci_lint2
Dockerfile: update golangci-lint to v2.10.1
2026-04-20 16:00:14 +02:00
Paweł GronowskiandSebastiaan van Stijn 58a7c3155b golangci-lint: fix lint failures from v2.10.1 upgrade
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-04-20 15:49:35 +02:00
Sebastiaan van Stijn f37a9e663f Dockerfile: update golangci-lint to v2.10.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 15:49:32 +02:00
Paweł GronowskiandGitHub 4e25e5cfa9 Merge pull request #6941 from vvoland/milestone-validate
gha: Add milestone validation workflow
2026-04-20 15:32:29 +02:00
Sebastiaan van Stijn ee56098b07 cmd/docker-trust: bump dependencies
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 14:59:01 +02:00
Sebastiaan van StijnandGitHub f31b8b2697 Merge pull request #6946 from thaJeztah/bump_runewidth
vendor: github.com/mattn/go-runewidth v0.0.23
2026-04-20 14:52:27 +02:00
Paweł Gronowski efbbc0c68c gha: Add milestone validation workflow
Ensure PRs have a milestone set and that it matches the next release
version declared in the root VERSION file.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-04-20 14:51:37 +02:00
Paweł GronowskiandGitHub 1546665853 Merge pull request #6942 from thaJeztah/rm_kernel_memory
docs, completion: remove deprecated "--kernel-memory" flags
2026-04-20 14:39:40 +02:00
Paweł GronowskiandGitHub 4e4e8f940b Merge pull request #6943 from thaJeztah/bump_creds_helper
vendor: github.com/docker/docker-credential-helpers v0.9.6
2026-04-20 14:38:45 +02:00
Paweł GronowskiandGitHub 2b65d5943f Merge pull request #6944 from thaJeztah/bump_x_deps
vendor: update golang.org/x/* dependencies
2026-04-20 14:38:27 +02:00
Sebastiaan van Stijn a75ab98e43 vendor: github.com/mattn/go-runewidth v0.0.23
full diff: https://github.com/mattn/go-runewidth/compare/v0.0.22...v0.0.23

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 14:34:25 +02:00
Sebastiaan van Stijn b9549d3ab5 vendor: golang.org/x/net v0.53.0
full diff: https://cs.opensource.google/go/x/net/+/refs/tags/v0.52.0...refs/tags/v0.53.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 14:22:26 +02:00
Sebastiaan van Stijn 2edf815a80 vendor: golang.org/x/term v0.42.0
full diff: https://cs.opensource.google/go/x/term/+/refs/tags/v0.41.0...refs/tags/v0.42.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 14:21:03 +02:00
Sebastiaan van Stijn 258010c788 vendor: golang.org/x/text v0.36.0
full diff: https://cs.opensource.google/go/x/text/+/refs/tags/v0.35.0...refs/tags/v0.36.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 14:20:06 +02:00
Sebastiaan van Stijn cfa80d8644 vendor: golang.org/x/mod v0.35.0
full diff: https://cs.opensource.google/go/x/mod/+/refs/tags/v0.34.0...refs/tags/v0.35.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 14:18:58 +02:00
Sebastiaan van Stijn c20b2479e0 vendor: golang.org/x/sys v0.43.0
full diff: https://cs.opensource.google/go/x/sys/+/refs/tags/v0.42.0...refs/tags/v0.43.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 14:17:33 +02:00
Sebastiaan van Stijn 573dd31bf7 vendor: github.com/docker/docker-credential-helpers v0.9.6
full diff: https://github.com/docker/docker-credential-helpers/compare/v0.9.5...v0.9.6

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 14:14:22 +02:00
Sebastiaan van Stijn 2988338b99 docs/reference: remove deprecated "--kernel-memory" flag
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 14:06:16 +02:00
Sebastiaan van Stijn 58c7328df6 man: remove deprecated "--kernel-memory" flag
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 14:05:48 +02:00
Sebastiaan van Stijn 5468871a02 contrib/completion: remove deprecated "--kernel-memory" flags
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 14:05:10 +02:00
Paweł GronowskiandGitHub 10f16704a3 Merge pull request #6931 from docker/dependabot/github_actions/docker/bake-action-7.1.0
build(deps): bump docker/bake-action from 7.0.0 to 7.1.0
2026-04-20 12:12:09 +02:00
Sebastiaan van StijnandGitHub 89958b190c Merge pull request #6927 from Varun5711/docs/legacy-plugin-links-6880
docs: refresh legacy plugin references
2026-04-17 20:24:32 +02:00
Varun5711 acbbeb3c3a docs: refresh legacy plugin references
Signed-off-by: Varun Hotani <varunhotani@gmail.com>
2026-04-17 21:14:16 +05:30
Paweł GronowskiandGitHub dea3ebaa87 Merge pull request #6936 from thaJeztah/bump_otels
vendor: google.golang.org/grpc v1.80.0, go.opentelemetry.io/otel v1.43.0, go.opentelemetry.io/contrib v0.68.0
2026-04-17 15:41:57 +02:00
Sebastiaan van Stijn 3362ae31d0 vendor: go.opentelemetry.io/contrib v0.68.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-17 14:09:30 +02:00
Sebastiaan van Stijn a1edb22832 vendor: go.opentelemetry.io/otel v1.43.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-17 14:07:46 +02:00
Sebastiaan van Stijn 0a24da3382 vendor: google.golang.org/grpc v1.80.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-17 14:07:39 +02:00
Sebastiaan van Stijn f24c779887 vendor: google.golang.org/genproto/* 9d38bb4040a9
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-17 14:04:40 +02:00
Paweł GronowskiandGitHub 17633b7742 Merge pull request #6935 from docker/dependabot/github_actions/actions/upload-artifact-7.0.1
build(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1
2026-04-17 10:52:20 +02:00
dependabot[bot]andGitHub 99aaff1807 build(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/bbbca2ddaa5d8feaa63e36b76fdaad77386f024f...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-17 08:43:30 +00:00
dependabot[bot]andSebastiaan van Stijn f3c060c382 build(deps): bump docker/bake-action from 7.0.0 to 7.1.0
Bumps [docker/bake-action](https://github.com/docker/bake-action) from 7.0.0 to 7.1.0.
- [Release notes](https://github.com/docker/bake-action/releases)
- [Commits](https://github.com/docker/bake-action/compare/82490499d2e5613fcead7e128237ef0b0ea210f7...a66e1c87e2eca0503c343edf1d208c716d54b8a8)

---
updated-dependencies:
- dependency-name: docker/bake-action
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-17 00:41:48 +02:00
Paweł GronowskiandGitHub 792293cc2f Merge pull request #6923 from docker/dependabot/github_actions/docker/login-action-4.1.0
build(deps): bump docker/login-action from 4.0.0 to 4.1.0
2026-04-16 13:10:41 +02:00
Paweł GronowskiandGitHub 2660a496bf Merge pull request #6930 from thaJeztah/bump_go_connections
vendor: github.com/docker/go-connections v0.7.0
2026-04-16 13:10:09 +02:00
Sebastiaan van Stijn b9a052a987 vendor: github.com/docker/go-connections v0.7.0
Changes:

- raise minimum supported Go version to go1.23
- sockets: `ConfigureTransport`: prevent idle connections leaking FDs.
- sockets: implement `WithAdditionalUsersAndGroups` for windows.
- tlsconfig: add ChaCha20-Poly1305 cipher suites to align closer with stdlib defaults.
- nat: SortPortMap: accept `map[Port][]PortBinding` as argument.
- nat: add benchmarks, optimize, and improve errors.
- proxy: check for `net.ErrClosed` instead of string-matching "use of closed network connection".

Breaking changes:

- tlsconfig: deprecate `tlsconfig.SystemCertPool` in favor of stdlib `x509.SystemCertPool`.
- sockets: remove deprecated `DialPipe`, `GetProxyEnv`, `DialerFromEnvironment`

Dependency updates:

- update github.com/Microsoft/go-winio to go v0.6.2
- update golang.org/x/sys to v0.10.0

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-16 00:20:20 +02:00
Sebastiaan van StijnandGitHub 84b357f6b0 Merge pull request #6929 from thaJeztah/stdlib_systempools
use stdlib's x509.SystemCertPool on Windows
2026-04-15 16:32:00 +02:00
Sebastiaan van Stijn 0d32fde8e3 internal/registry: use stdlib's x509.SystemCertPool on Windows
The `tlsconfig.SystemCertPool` utility in go-connections was added in
[docker/go-connections@55aadc3], at which time Go stdlib didn't support
system-pools ([x509.SystemCertPool]) on Windows, so an empty pool was
constructed.

Support for system pools on Windows originally added in Go 1.8 (through
[golang/go@05471e9]), but reverted, and re-implemented in Go 1.18 (through
[golang/go@3544082]).

Go 1.18 and up now implement this, but, unlike Linux, which uses a pure-Go
implementation, certificate validation is handled by the system:

> On macOS and Windows, certificate verification is handled by system APIs,
> but the package aims to apply consistent validation rules across operating
> systems.

On macOS and Windows, x509.SystemCertPool returns an empty Pool, with the
`systemPool` set to `true` (see [loadSystemRoots]). This must be considered
an implementation detail; custom CAs can be appended to this pool, and handled
as usual.

This patch removes the special handling on Windows, removing the dependency
on go-connections for this part.

[docker/go-connections@55aadc3]: https://github.com/docker/go-connections/commit/55aadc3cc561684699edcdd0921b9293c3ee6b49
[golang/go@05471e9]: https://github.com/golang/go/commit/05471e9ee64a300bd2dcc4582ee1043c055893bb
[golang/go@3544082]: https://github.com/golang/go/commit/3544082f75fd3d2df7af237ed9aef3ddd499ab9c
[x509.SystemCertPool]: https://pkg.go.dev/crypto/x509#SystemCertPool
[loadSystemRoots]: https://cs.opensource.google/go/go/+/refs/tags/go1.26.1:src/crypto/x509/root_windows.go;l=15-17

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-15 13:04:15 +02:00
Sebastiaan van Stijn ad641e5d61 cmd/docker-trust: use stdlib's x509.SystemCertPool on Windows
The `tlsconfig.SystemCertPool` utility in go-connections was added in
[docker/go-connections@55aadc3], at which time Go stdlib didn't support
system-pools ([x509.SystemCertPool]) on Windows, so an empty pool was
constructed.

Support for system pools on Windows originally added in Go 1.8 (through
[golang/go@05471e9]), but reverted, and re-implemented in Go 1.18 (through
[golang/go@3544082]).

Go 1.18 and up now implement this, but, unlike Linux, which uses a pure-Go
implementation, certificate validation is handled by the system:

> On macOS and Windows, certificate verification is handled by system APIs,
> but the package aims to apply consistent validation rules across operating
> systems.

On macOS and Windows, x509.SystemCertPool returns an empty Pool, with the
`systemPool` set to `true` (see [loadSystemRoots]). This must be considered
an implementation detail; custom CAs can be appended to this pool, and handled
as usual.

This patch removes the special handling on Windows, removing the dependency
on go-connections for this part.

[docker/go-connections@55aadc3]: https://github.com/docker/go-connections/commit/55aadc3cc561684699edcdd0921b9293c3ee6b49
[golang/go@05471e9]: https://github.com/golang/go/commit/05471e9ee64a300bd2dcc4582ee1043c055893bb
[golang/go@3544082]: https://github.com/golang/go/commit/3544082f75fd3d2df7af237ed9aef3ddd499ab9c
[x509.SystemCertPool]: https://pkg.go.dev/crypto/x509#SystemCertPool
[loadSystemRoots]: https://cs.opensource.google/go/go/+/refs/tags/go1.26.1:src/crypto/x509/root_windows.go;l=15-17

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-15 13:03:39 +02:00
dependabot[bot]andSebastiaan van Stijn 14aa781767 build(deps): bump docker/login-action from 4.0.0 to 4.1.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/b45d80f862d83dbcd57f89517bcf500b2ab88fb2...4907a6ddec9925e35a0a9e82d7399ccc52663121)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-15 11:40:10 +02:00
Paweł GronowskiandGitHub 950401cf07 Merge pull request #6920 from thaJeztah/bump_go1.26.2
update to Go 1.26.2
2026-04-08 12:49:00 +02:00
Sebastiaan van StijnandGitHub 3ba1094cb1 Merge pull request #6915 from docker/dependabot/github_actions/github/codeql-action-4.35.1
build(deps): bump github/codeql-action from 4.34.1 to 4.35.1
2026-04-08 01:03:53 +02:00
Sebastiaan van Stijn d1920f0b2c update to Go 1.26.2
go1.26.2 (released 2026-04-07) includes security fixes to the go command,
the compiler, and the archive/tar, crypto/tls, crypto/x509, html/template,
and os packages, as well as bug fixes to the go command, the go fix command,
the compiler, the linker, the runtime, and the net, net/http, and net/url
packages. See the Go 1.26.2 milestone on our issue tracker for details;

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

From the security announce:

We have just released Go versions 1.26.2 and 1.25.9, minor point releases.

These releases include 10 security fixes following the security policy:

- os: Root.Chmod can follow symlinks out of the root on Linux

  On Linux, if the target of Root.Chmod is replaced with a symlink while
  the chmod operation is in progress, Chmod could operate on the target
  of the symlink, even when the target lies outside the root.

  The Linux fchmodat syscall silently ignores the AT_SYMLINK_NOFOLLOW flag,
  which Root.Chmod uses to avoid symlink traversal. Root.Chmod checks its
  target before acting and returns an error if the target is a symlink
  lying outside the root, so the impact is limited to cases where the
  target is replaced with a symlink between the check and operation.

  On Linux, Root.Chmod now uses the fchmodat2 syscall when available, and
  an workaround using /proc/self/fd otherwise.

  Thanks to Uuganbayar Lkhamsuren for reporting this issue.

  This is CVE-2026-32282 and Go issue https://go.dev/issue/78293.

- html/template: JS template literal context incorrectly tracked

  Context was not properly tracked across template branches for JS template
  literals, leading to possibly incorrect escaping of content when branches were
  used.

  Additionally template actions within JS template literals did not properly
  track
  the brace depth, leading to incorrect escaping being applied.

  These issues could cause actions within JS template literals to be incorrectly
  or improperly escaped, leading to XSS vulnerabilities.

  This only affects templates that use template actions within JS template
  literals.

  This is CVE-2026-32289 and Go issue https://go.dev/issue/78331.

- crypto/x509: excluded DNS constraints not properly applied to wildcard domains

  When verifying a certificate chain containing excluded DNS constraints, these
  constraints are not correctly applied to wildcard DNS SANs which use a
  different
  case than the constraint.

  For example, if a certificate contains the DNS name "*.example.com" and the
  excluded DNS name "EXAMPLE.COM", the constraint will not be applied.

  This only affects validation of otherwise trusted certificate chains, issued
  by
  a root CA in the VerifyOptions.Roots CertPool, or in the system certificate
  pool.

  This issue only affects Go 1.26.

  Thank you to Riyas from Saintgits College of Engineering, k1rnt, @1seal for
  reporting this issue.

  This is CVE-2026-33810 and Go issue https://go.dev/issue/78332.

- cmd/compile: no-op interface conversion bypasses overlap checking

  Previously, the compiler failed to unwrap pointers contained within
  a no-op interface conversion leading to an incorrect determination
  of a non-overlapping move.

  To prevent unsafe move operations, the compiler will now unwrap all
  such conversions before considering a move non-overlapping.

  Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.

  This is CVE-2026-27144 and Go issue https://go.dev/issue/78371.

- cmd/compile: possible memory corruption after bound check elimination

  Previously, slices and arrays accessed using induction variables
  were sometimes incorrectly proved in-bound. If the induction variable
  used for indexing were to overflow or underflow, it could allow access
  to memory beyond the scope of the original slice or array.

  To prevent this behavior, the compiler ensures that any mutated induction
  variable that overflows/underflows with respect to its loop condition
  is not used for bound check elimination.

  Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.

  This is CVE-2026-27143 and Go issue https://go.dev/issue/78333.

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

  tar.Reader could allocate an unbounded amount of memory when reading
  a maliciously-crafted archive containing a large number of sparse
  regions encoded in the "old GNU sparse map" format.

  We now limit both the number of old GNU sparse map extension blocks,
  and the total number of sparse file entries, regardless of encoding.

  Thanks to Colin Walters (wal...@verbum.org) who initially reported this issue.
  Thanks also to Uuganbayar Lkhamsuren (https://github.com/uug4na) and Jakub
  Ciolek
  who additionally reported this issue.

  This is CVE-2026-32288 and Go issue https://go.dev/issue/78301.

- crypto/tls: multiple key update handshake messages can cause connection to
  deadlock

  If one side of the TLS connection sends multiple key update messages
  post-handshake in a single record, the connection can deadlock, causing
  uncontrolled consumption of resources. This can lead to a denial of service.

  This only affects TLS 1.3.

  Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.

  This is CVE-2026-32283 and Go issue https://go.dev/issue/78334.

- cmd/go: trust layer bypass when using cgo and SWIG

  A well-crafted SWIG source file could take advantage
  of a file-naming convention used inside the trust
  boundary of the cgo compiler. Doing so could result
  in arbitrary code execution during build time.

  SWIG files are disallowed from using this convention.

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

  This is CVE-2026-27140 and Go issue https://go.dev/issue/78335.

- crypto/x509: unexpected work during chain building

  During chain building, the amount of work that is done is not correctly
  limited
  when a large number of intermediate certificates are passed in
  VerifyOptions.Intermediates, which can lead to a denial of service. This
  affects
  both direct users of crypto/x509 and users of crypto/tls.

  Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.

  This is CVE-2026-32280 and Go issue https://go.dev/issue/78282.

- crypto/x509: inefficient policy validation

  Validating certificate chains which use policies is unexpectedly inefficient
  when certificates in the chain contain a very large number of policy mappings,
  possibly causing denial of service.

  This only affects validation of otherwise trusted certificate chains, issued
  by
  a root CA in the VerifyOptions.Roots CertPool, or in the system certificate
  pool.

  Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.

  This is CVE-2026-32281 and Go issue https://go.dev/issue/78281.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-08 00:45:45 +02:00
dependabot[bot]andGitHub b23c1a2d76 build(deps): bump github/codeql-action from 4.34.1 to 4.35.1
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.34.1 to 4.35.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/38697555549f1db7851b81482ff19f1fa5c4fedc...c10b8064de6f491fea524254123dbe5e09572f13)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 08:45:04 +00:00
Sebastiaan van StijnandGitHub 9d7ad9ff18 Merge pull request #6911 from thaJeztah/bump_modules
vendor: moby/api v1.54.1, moby/client v0.4.0
2026-04-03 16:24:17 +02:00
Sebastiaan van Stijn c88681f8d8 vendor: moby/api v1.54.1, moby/client v0.4.0
full diffs:

- https://github.com/moby/moby/compare/ef0a1e449505...api/v1.54.1
- https://github.com/moby/moby/compare/ef0a1e449505...client/v0.4.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-03 15:54:11 +02:00
Paweł GronowskiandGitHub 84b884f383 Merge pull request #6909 from thaJeztah/update_authors_mailmap
update AUTHORS and .mailmap
2026-04-03 13:53:21 +02:00
Paweł GronowskiandGitHub d6169a5ea9 Merge pull request #6910 from thaJeztah/update_version
bump version to v29.4.0-dev
2026-04-03 13:46:49 +02:00
Sebastiaan van Stijn 5ddc1553ae bump version to v29.4.0-dev
This file is only used as default if no version is specified. We
should probably get rid of this, but let's update it to better
reflect the version that developer builds are building.

https://github.com/docker/cli/blob/d48fb9f9f7bdb6e0ef37dbde68612a1704cad46e/docker.Makefile#L22

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-03 13:28:17 +02:00
Sebastiaan van Stijn a347d9e103 update AUTHORS and .mailmap
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-03 13:26:29 +02:00
Sebastiaan van StijnandGitHub 699b029b57 Merge pull request #6908 from thaJeztah/bump_runewidth
vendor: github.com/mattn/go-runewidth v0.0.22
2026-04-03 12:58:59 +02:00
Paweł GronowskiandGitHub 512607a396 Merge pull request #6889 from YoanWai/docs/prune-filter-behavior
docs: clarify multiple --filter behavior in prune commands
2026-04-03 12:53:43 +02:00
Sebastiaan van Stijn 5fca671ef4 vendor: github.com/mattn/go-runewidth v0.0.22
full diff: https://github.com/mattn/go-runewidth/compare/v0.0.21...v0.0.22

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-03 12:53:08 +02:00
Paweł GronowskiandGitHub 753b10228f Merge pull request #6893 from thaJeztah/bump_moby
vendor: moby/client and moby/api master
2026-04-03 12:49:36 +02:00
Sebastiaan van Stijn 42da40a605 vendor: moby/client and moby/api master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-03 12:44:53 +02:00
Paweł GronowskiandGitHub 7d4f9bd581 Merge pull request #6872 from thaJeztah/link_completions
shell completions: add shell completion for `docker rm --link` and exclude legacy links for container names
2026-04-03 12:37:11 +02:00
Paweł GronowskiandGitHub 2daa2c31e8 Merge pull request #6876 from thaJeztah/stats_optimize
docker stats: assorted fixes and optimizations in rendering
2026-04-03 12:34:04 +02:00
Paweł GronowskiandGitHub d8a85fba27 Merge pull request #6875 from thaJeztah/optimize_formatter
cli/command/formatter: assorted fixes and cleanups
2026-04-03 12:32:39 +02:00
Sebastiaan van StijnandGitHub efddff6549 Merge pull request #6907 from docker/dependabot/github_actions/codecov/codecov-action-6.0.0
build(deps): bump codecov/codecov-action from 5.5.3 to 6.0.0
2026-04-03 12:32:27 +02:00
Paweł GronowskiandGitHub 72beec9840 Merge pull request #6906 from thaJeztah/stream_preserve_file
cli/streams: Out, In: preserve original os.File when available
2026-04-03 12:29:30 +02:00
dependabot[bot]andGitHub 0029d5936a build(deps): bump codecov/codecov-action from 5.5.3 to 6.0.0
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.3 to 6.0.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/1af58845a975a7985b0beb0cbe6fbbb71a41dbad...57e3a136b779b570ffcdbf80b3bdc90e7fab3de2)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-03 08:44:33 +00:00
Sebastiaan van Stijn e7cbaafa9d cli/command/container: statsFormatWrite: inline render func
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 22:00:04 +02:00
Sebastiaan van Stijn c44a4d9758 cli/command/container: RunStats: avoid bytes to strings conversions
This code is using a `bytes.Buffer` to render the stats, before writing
the results to the CLI's output. Let's try to use bytes where possible
instead of converting to a string;

- Use the buffer's `Write` (and `Out().Write`) to write directly to the
  buffer/writer where possible.
- Use `io.WriteString` instead of `fmt.Printf`
- Use `bytes.SplitSeq` instead of `strings.SplitSeq`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 21:59:49 +02:00
Sebastiaan van Stijn d92d1187fc cli/command/container: RunStats: rename buffer var for brevity
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 21:59:10 +02:00
Sebastiaan van Stijn ee88c60a5e cli/command/container: stats: add snapshot method
Move logic to capture a snapshot of the current stats to the stats struct.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 21:59:05 +02:00
Sebastiaan van Stijn 4c5efd61ea cli/command/container: fix buffer reuse when printing stats
Don't write lines back into the same buffer that's being read from when
clearing lines; add a separate output buffer to construct the output,
then write it to the CLI's output at once (to prevent terminal flicker).

Relates to / introduced in cb2f95ceee.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 21:50:31 +02:00
Sebastiaan van Stijn b309524f60 cli/command/formatter: NewStats: update GoDoc and add TODO
Update the GoDoc to better align with the actual implementation. The
"idOrName" is used for fuzzy-matching the container, which can result
in multiple stats for the same container:

    docker ps --format 'table {{.ID}}\t{{.Names}}'
    CONTAINER ID   NAMES
    b49e6c21d12e   quizzical_maxwell

    docker stats --no-stream quizzical_maxwell b49e6c21d12e b49e6
    CONTAINER ID   NAME                CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O        PIDS
    b49e6c21d12e   quizzical_maxwell   0.10%     140.8MiB / 7.653GiB   1.80%     3.11MB / 13.4kB   115MB / 1.12MB   28
    b49e6c21d12e   quizzical_maxwell   0.10%     140.8MiB / 7.653GiB   1.80%     3.11MB / 13.4kB   115MB / 1.12MB   28
    b49e6c21d12e   quizzical_maxwell   0.10%     140.8MiB / 7.653GiB   1.80%     3.11MB / 13.4kB   115MB / 1.12MB   28

We should resolve the canonical ID once, then use that as reference
to prevent duplicates. Various  parts in the code compare Container
against "ID" only (not considering "name" or "ID-prefix").

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 21:11:48 +02:00
Sebastiaan van Stijn abd2e211b9 cli/command/formatter: add Format.templateString, remove Context.preFormat
The `Context.preFormat` method normalizes the Format as given by the user,
and handles (e.g.) stripping the "table" prefix and replacing the "json"
format for the actual format (`{{json .}}`).

The method used a `finalFormat` field on the Context as intermediate,
and was required to be called before executing the format.

This patch adds a `Format.templateString()` method that returns the
parsed format instead of storing it on the Context. It is currently
not exported, but something we could consider in future.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 21:11:40 +02:00
Sebastiaan van Stijn cb615a9772 cli/command/formatter: Context.postFormat: remove redundant buffer
A tabwriter is backed by a buffer already, because it needs to re-flow columns
based on content written to it. This buffer was added in [moby@ea61dac9e6] as
part of a new feature to allow for custom delimiters; neither the patch, nor
code-review on the PR mention the extra buffer, so it likely was just overlooked.

This patch;

- removes the redundant buffer
- adds an early return for cases where no tabwriter is used.

[moby@ea61dac9e6]: https://github.com/moby/moby/commit/ea61dac9e6d04879445f9c34729055ac1bb15050

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 20:59:30 +02:00
Sebastiaan van Stijn f7a909d56b cli/command/formatter: optimize ContainerContext.Names
Optimize formatting of container name(s);

- Inline `StripNamePrefix` in the loop, so that we don't have to
  construct a new slice with names (in most cases only to pick
  the first one).
- Don't use `strings.Split`, as it allocates a new slice and we only
  used it to check if the container-name was a legacy-link (contained
  slashes).
- Use a string-builder to concatenate names when not truncating instead
  of using an intermediate slice (and `strings.Join`).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 20:59:29 +02:00
Sebastiaan van Stijn 94d4929a04 cli/streams: Out, In: preserve original os.File when available
Preserve the original *os.File, if available, and add a File() method
to return it.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 20:52:30 +02:00
Sebastiaan van StijnandGitHub 2cc9fe1438 Merge pull request #6900 from thaJeztah/cli_stream_cleanups
cli/streams: assorted cleanups
2026-04-02 20:52:06 +02:00
Paweł GronowskiandGitHub 38e44e4125 Merge pull request #6905 from thaJeztah/bump_trust_deps
cmd/docker-trust: bump dependencies
2026-04-02 18:15:34 +02:00
Sebastiaan van Stijn 526dfffc26 cli/streams: simplify CheckTty
This function is very specific to attaching to containers, and probably
helps clarity to inline it where used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 18:11:50 +02:00
Sebastiaan van Stijn 48721c2340 cli/streams: don't depend on embedding
Define explicit wrapper methods instead of depending on the embedded
commonStreams struct.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 17:29:55 +02:00
Sebastiaan van Stijn 39e82e6524 cli/streams: move constructors to the start
It's more idiomatic to define the constructor before methods.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 17:29:55 +02:00
Sebastiaan van Stijn 34805dd013 cli/streams: (In|Out).SetRawTerminal: dry
Move the code to the commonStream type, which is where the actual
state field is kept.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 17:29:55 +02:00
Sebastiaan van Stijn 6e1f03c2e0 cmd/docker-trust: bump dependencies
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 17:06:57 +02:00
Sebastiaan van StijnandGitHub 7639343b30 Merge pull request #6867 from docker/update-swarm-docs
Updated example tokens in swarm docs
2026-04-02 15:42:59 +02:00
87a222158d Updated example tokens in swarm docs
Prevent security scanners from detecting them as eaked secrets.

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Alexandre Vallières-Lagacé <alexandre.valliereslagace@docker.com>
Signed-off-by: Alexandre Vallières-Lagacé <alexandre@vallier.es>
2026-04-02 15:25:16 +02:00
Paweł GronowskiandGitHub fbfb69f7e9 Merge pull request #6873 from thaJeztah/simplify_chips
cli/command/image: getPossibleChips: simplify
2026-04-02 13:51:17 +02:00
Paweł GronowskiandGitHub 424955ddf7 Merge pull request #6904 from thaJeztah/bump_otels
vendor: go.opentelemetry.io/otel v1.42.0, otel/contrib v1.67.0
2026-04-02 13:43:10 +02:00
Paweł GronowskiandGitHub bbb6311c09 Merge pull request #6871 from thaJeztah/completions_no_dups
cli/command/completion: don't provide duplicate completions
2026-04-02 13:41:27 +02:00
Sebastiaan van StijnandGitHub 48afa03bba Merge pull request #6878 from zampani-docker/zampani/fix-plugin-force-exit-race
fix(cmd/docker): prevent race between force-exit goroutine and plugin wait
2026-04-02 12:54:38 +02:00
Sebastiaan van Stijn 98d978df6b vendor: go.opentelemetry.io/otel v1.42.0, otel/contrib v1.67.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 12:51:19 +02:00
Sebastiaan van Stijn a48ff6b591 cli/command/completion: don't provide duplicate completions
When completing for commands that accept multiple arguments, we did
not remove suggestions that were already consumed. This could be
confusing if there was only 1 suggestion, in which case every `<tab>`
would automatically suggest the same name again:

docker rm -fv magical_lumiere magical_lumiere  magical_lumiere

This patch adds a "Unique" helper to wrap a completion func to remove
completion results that are already consumed (i.e., appear in "args").

For example:

    # initial completion: args is empty, so all results are shown
    command <tab>
    one two three

    # "one" is already used so omitted
    command one <tab>
    two three

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 12:46:41 +02:00
Paweł GronowskiandGitHub 12b8ca4406 Merge pull request #6901 from thaJeztah/modernize
cli/command/formatter: modernize
2026-04-02 12:34:14 +02:00
Paweł GronowskiandGitHub dc9b6e553e Merge pull request #6874 from thaJeztah/future_proof_prefix
formatting: only strip "/" prefixes
2026-04-02 11:42:31 +02:00
Paweł GronowskiandGitHub f4d2906b56 Merge pull request #6894 from Rohan5commit/docs/fix-run-following-typo-20260331
docs: fix typo in run reference examples
2026-04-02 11:40:50 +02:00
Paweł GronowskiandGitHub 6998987257 Merge pull request #6866 from thaJeztah/bump_x_deps
vendor: update golang.org/x/* dependencies
2026-04-02 11:39:38 +02:00
Paweł GronowskiandGitHub 5e856302bf Merge pull request #6899 from thaJeztah/bump_compress
vendor: github.com/klauspost/compress v1.18.5
2026-04-02 11:37:51 +02:00
Paweł GronowskiandGitHub 253dc62658 Merge pull request #6903 from thaJeztah/bump_jose
vendor: github.com/go-jose/go-jose/v4 v4.1.4
2026-04-02 11:36:52 +02:00
Paweł GronowskiandGitHub a9ea8b23fa Merge pull request #6825 from thaJeztah/bump_go1.26
update to go1.26.1
2026-04-02 11:32:46 +02:00
Sebastiaan van Stijn 091afa4957 vendor: github.com/go-jose/go-jose/v4 v4.1.4
Fixes CVE-2026-34986 / GHSA-78h2-9frx-2jm8

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 02:39:43 +02:00
Sebastiaan van Stijn 968ad0ea6c vendor: golang.org/x/net v0.52.0
full diff: https://cs.opensource.google/go/x/net/+/refs/tags/v0.50.0...refs/tags/v0.52.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 02:31:42 +02:00
Sebastiaan van Stijn 78fb018754 vendor: golang.org/x/time v0.15.0
full diff: https://cs.opensource.google/go/x/time/+/refs/tags/v0.14.0...refs/tags/v0.15.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 02:29:46 +02:00
Sebastiaan van Stijn 18739a5ef6 vendor: golang.org/x/term v0.41.0
full diff: https://cs.opensource.google/go/x/term/+/refs/tags/v0.40.0...refs/tags/v0.41.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 02:29:45 +02:00
Sebastiaan van Stijn 9b21846cde vendor: golang.org/x/text v0.35.0
full diff: https://cs.opensource.google/go/x/text/+/refs/tags/v0.34.0...refs/tags/v0.35.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 02:29:45 +02:00
Sebastiaan van Stijn c22bf3c77e vendor: golang.org/x/mod v0.34.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 02:29:32 +02:00
Sebastiaan van Stijn d792fc53b7 vendor: golang.org/x/sync v0.20.0
full diff: https://cs.opensource.google/go/x/sync/+/refs/tags/v0.19.0...refs/tags/v0.20.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 02:17:17 +02:00
Sebastiaan van Stijn e9664a72ea vendor: golang.org/x/sys v0.42.0
full diff: https://cs.opensource.google/go/x/sys/+/refs/tags/v0.41.0...refs/tags/v0.42.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 02:17:17 +02:00
Sebastiaan van Stijn fb776458cb update to go1.26.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 02:12:08 +02:00
Sebastiaan van Stijn 62d80156e1 ci: pin remaining actions
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-02 02:11:26 +02:00
Sebastiaan van StijnandGitHub a3c4d64755 Merge pull request #6897 from thaJeztah/bump_minimum_go
update minimum go version to go1.25
2026-04-01 20:12:45 +02:00
Paweł GronowskiandGitHub eecf81316d Merge pull request #6898 from thaJeztah/bump_patternmatcher
vendor: github.com/moby/patternmatcher v0.6.1
2026-04-01 18:39:33 +02:00
Paweł GronowskiandGitHub e67dba1189 Merge pull request #6896 from thaJeztah/bump_grpc
vendor: google.golang.org/grpc v1.79.3
2026-04-01 18:39:30 +02:00
Paweł GronowskiandGitHub fea2465d65 Merge pull request #4723 from thaJeztah/govalidator
ci: add module compatibility check
2026-04-01 16:37:45 +02:00
Sebastiaan van Stijn ea74248e8e cli/command/formatter: modernize
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-01 15:43:34 +02:00
Sebastiaan van Stijn 5efed5fa30 vendor: github.com/klauspost/compress v1.18.5
- zstd: Fix crash when changing encoder dictionary with same ID

full diff: https://github.com/klauspost/compress/compare/v1.18.4...v1.18.5

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-01 00:38:50 +02:00
Sebastiaan van Stijn cd9e5ae84f vendor: github.com/moby/patternmatcher v0.6.1
- fix panic / nil pointer dereference on invalid patterns

full diff: https://github.com/moby/patternmatcher/compare/v0.6.0...v0.6.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-01 00:24:02 +02:00
Sebastiaan van Stijn bf6a1e1fcf cli-plugins/socket: modernize
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-01 00:19:32 +02:00
Sebastiaan van Stijn 8f7dc04070 update minimum go version to go1.25
- drop support for go1.24
- update vendor.mod to go1.25.0
- update //go:build tags to go1.25

The golang.org/x/ dependencies now require go1.25 as a minimum,
so updating our build tags accordingly.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-01 00:19:04 +02:00
Sebastiaan van Stijn a14db81c9c vendor: google.golang.org/grpc v1.79.3
fixes [CVE-2026-33186] / [GHSA-p77j-4mvh-x3m3]

full diff: https://github.com/grpc/grpc-go/compare/v1.78.0...v1.79.3

[CVE-2026-33186]: https://www.cve.org/CVERecord?id=CVE-2026-33186
[GHSA-p77j-4mvh-x3m3]: https://github.com/grpc/grpc-go/security/advisories/GHSA-p77j-4mvh-x3m3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-31 09:22:30 +02:00
Codex e660030f3a docs: fix typo in run reference
Signed-off-by: Codex <codex@openai.com>
2026-03-31 10:31:44 +08:00
Sebastiaan van Stijn 58c1585b49 gha: validate gocompat
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-26 17:53:31 +01:00
Sebastiaan van Stijn ea42337d01 implement module compatibility check
This package imports all "importable" packages, i.e., packages that:

- are not applications ("main")
- are not internal
- and that have non-test go-files

We do this to verify that our code can be consumed as a dependency
in "module mode". When using a dependency that does not have a go.mod
(i.e.; is not a "module"), go implicitly generates a go.mod. Lacking
information from the dependency itself, it assumes "go1.16" language
(see [DefaultGoModVersion]). Starting with Go1.21, go downgrades the
language version used for such dependencies, which means that any
language feature used that is not supported by go1.16 results in a
compile error;

    # github.com/docker/cli/cli/context/store
    /go/pkg/mod/github.com/docker/cli@v25.0.0-beta.2+incompatible/cli/context/store/storeconfig.go:6:24: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    /go/pkg/mod/github.com/docker/cli@v25.0.0-beta.2+incompatible/cli/context/store/store.go:74:12: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)

These errors do NOT occur when using GOPATH mode, nor do they occur
when using "pseudo module mode" (the "-mod=mod -modfile=vendor.mod"
approach used in this repository).

As a workaround for this situation, we must include "//go:build" comments
in any file that uses newer go-language features (such as the "any" type
or the "min()", "max()" builtins).

From the go toolchain docs (https://go.dev/doc/toolchain):

> The go line for each module sets the language version the compiler enforces
> when compiling packages in that module. The language version can be changed
> on a per-file basis by using a build constraint.
>
> For example, a module containing code that uses the Go 1.21 language version
> should have a go.mod file with a go line such as go 1.21 or go 1.21.3.
> If a specific source file should be compiled only when using a newer Go
> toolchain, adding //go:build go1.22 to that source file both ensures that
> only Go 1.22 and newer toolchains will compile the file and also changes
> the language version in that file to Go 1.22.

This file is a generated module that imports all packages provided in
the repository, which replicates an external consumer using our code
as a dependency in go-module mode, and verifies all files in those
packages have the correct "//go:build <go language version>" set.

To test this package:

    make shell
    make -C ./internal/gocompat/
    make: Entering directory '/go/src/github.com/docker/cli/internal/gocompat'
    GO111MODULE=off go generate .
    GO111MODULE=on go mod tidy
    GO111MODULE=on go test -v
    # github.com/docker/cli/templates
    ../../templates/templates.go:13:17: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    # github.com/docker/cli/cli/compose/template
    ../../cli/compose/template/template.go:98:45: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/template/template.go:105:27: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/template/template.go:141:28: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    # github.com/docker/cli/cli/compose/types
    ../../cli/compose/types/types.go:53:22: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/types/types.go:86:34: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/types/types.go:105:22: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/types/types.go:137:34: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/types/types.go:211:20: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/types/types.go:343:35: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/types/types.go:442:40: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/types/types.go:469:24: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/types/types.go:490:24: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/types/types.go:587:28: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/types/types.go:442:40: too many errors
    # github.com/docker/cli/cli/context/store
    ../../cli/context/store/storeconfig.go:6:24: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/context/store/store.go:74:12: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/context/store/store.go:75:23: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/context/store/metadatastore.go:43:58: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/context/store/metadatastore.go:48:22: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/context/store/metadatastore.go:80:30: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    # github.com/docker/cli/cli/command/idresolver
    ../../cli/command/idresolver/idresolver.go:6:2: "github.com/docker/docker/api/types" imported and not used
    ../../cli/command/idresolver/idresolver.go:7:2: "github.com/docker/docker/api/types/swarm" imported and not used
    ../../cli/command/idresolver/idresolver.go:9:2: "github.com/pkg/errors" imported and not used
    ../../cli/command/idresolver/idresolver.go:28:49: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/command/idresolver/idresolver.go:58:53: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    # github.com/docker/cli/cli/compose/schema
    ../../cli/compose/schema/schema.go:20:46: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/schema/schema.go:27:53: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/schema/schema.go:45:32: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/compose/schema/schema.go:66:33: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
    FAIL	gocompat [build failed]
    make: *** [Makefile:3: verify] Error 1
    make: Leaving directory '/go/src/github.com/docker/cli/internal/gocompat'

[DefaultGoModVersion]: https://github.com/golang/go/blob/58c28ba286dd0e98fe4cca80f5d64bbcb824a685/src/cmd/go/internal/gover/version.go#L15-L24
[2]: https://go.dev/doc/toolchain

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-26 17:53:31 +01:00
Paweł GronowskiandGitHub 538ee85f39 Merge pull request #6888 from thaJeztah/add_build_tags
cli-plugins/hooks: add missing "go:build" comments
2026-03-26 16:32:44 +01:00
Yoan Wainmann d573c171fe docs: clarify multiple --filter behavior in prune commands
Document how multiple --filter flags interact in prune commands:
different filter keys are ANDed (all conditions must match), while
multiple values for the same key are ORed (any value can match).

This addresses a gap in the documentation where users could not
determine whether multiple filters were combined with AND or OR
logic, which is especially important for prune commands where
the wrong assumption could lead to unintended data removal.

Closes #5899

Signed-off-by: Yoan Wainmann <yoan@mreshet.co.il>
2026-03-25 18:13:49 +02:00
Sebastiaan van Stijn 07bb479a45 cli-plugins/hooks: add missing "go:build" comments
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 16:32:18 +01:00
Paweł GronowskiandGitHub 9637f1b364 Merge pull request #6886 from thaJeztah/pin_actions
ci: pin actions to digests
2026-03-25 15:22:16 +01:00
Sebastiaan van Stijn 97b9e04a94 ci: pin actions to digests
As a follow-up, we should use the full version (major.minor.patch).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 14:58:57 +01:00
Michael ZampaniandClaude Sonnet 4.6 2bc4307816 fix(cmd/docker): prevent race between force-exit goroutine and plugin wait
When a plugin ignores context cancellation and the user sends 3 SIGINTs,
the CLI kills the plugin with SIGKILL. Previously the signal goroutine
called os.Exit(1) directly; a race existed where plugincmd.Run() could
return first (plugin was SIGKILL'd, so ws.ExitStatus() = -1) and the
main goroutine would call os.Exit(-1) = exit code 255 before the
goroutine reached os.Exit(1).

Fix by moving exit-code ownership to the main goroutine. The signal
goroutine closes forceExitCh before calling Kill(), guaranteeing the
channel is closed before plugincmd.Run() returns (the plugin can only
die after Kill() delivers SIGKILL; Run() only returns after the process
is reaped). The main goroutine checks forceExitCh after Run() returns
and performs the print + os.Exit(1) itself.

Also return from the signal goroutine after the force-kill to prevent
further loop iterations from calling close(forceExitCh) a second time
(which would panic), in case additional signals arrive while the kill
is in flight.

Fixes a flaky failure in TestPluginSocketCommunication/detached/
the_main_CLI_exits_after_3_signals where exit code 255 was observed
instead of 1 on loaded CI runners (RC Docker on Alpine).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Michael Zampani <michael.zampani@docker.com>
2026-03-21 15:31:12 -07:00
Sebastiaan van Stijn 9c117d3c5d cli/command/container: add shell completion for docker rm --link
When linking containers through legacy links, a container can get multiple
names; its own name, and a name for each link it's providing:

    # create two containers with links between them
    docker run -d --name one nginx:alpine
    docker run -d --name two --link one:link1 --link one:link2 --link one:link3 nginx:alpine

    docker rm --link <tab>
    two/link1 two/link2 two/link3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-20 18:24:19 +01:00
Sebastiaan van Stijn df57ff7201 cli/command/completion: ContainerNames: skip legacy link names
Inline StripNamePrefix and skip legacy links for completion.
Legacy links can be removed from a container, but only when
using `docker [container] rm --link <link-name>`.

When linking containers through legacy links, a container can get multiple
names; its own name, and a name for each link it's providing:

    # create two containers with links between them

    docker run -d --name one nginx:alpine
    docker run -d --name two --link one:link1 --link one:link2 --link one:link3 nginx:alpine

    # container "one" now has multiple names
    docker ps --no-trunc --format '{{.Names}}'
    two
    one,two/link1,two/link2,two/link3

    # running `docker rm --link` with a link-name removes a link:

    docker rm --link two/link3
    docker ps --no-trunc --format '{{.Names}}'
    two
    one,two/link1,two/link2

    # but without `--link`, it resolves the linked container and removes it:
    docker rm -fv two/link2
    two/link2
    docker ps --no-trunc --format '{{.Names}}'
    two

Legacy links are deprecated, and this can be confusing, so let's not provide
completion for secondary names.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-20 18:17:43 +01:00
Sebastiaan van Stijn dfda342e51 cli/command/formatter: StripNamePrefix only strip "/" prefix
This code was assuming the API always returns container names with
a "/" prefix. While this is currently correct, we may at some point
stop doing so.

This patch changes the code to only trim "/" as prefix and not any
other character.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-20 17:43:37 +01:00
Sebastiaan van Stijn b5efd66ba6 cli/command/container: stats: make stripping "/" prefix deterministic
This code was assuming the API always returns container names with
a "/" prefix. While this is currently correct, we may at some point
stop doing so.

This patch changes the code to only trim "/" as prefix and not any
other character.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-20 17:43:36 +01:00
Sebastiaan van Stijn 64c8d68045 cli/command/image: getPossibleChips: simplify
Extract the check to a closure that filters in-place, and
run the check in a single loop.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-20 17:33:14 +01:00
Sebastiaan van StijnandGitHub 7922984193 Merge pull request #6863 from thaJeztah/fix_stats
fix: docker stats --all: remove containers when removed
2026-03-19 15:44:15 +01:00
Sebastiaan van StijnandGitHub bcfb717a31 Merge pull request #6859 from thaJeztah/plugin_metadata
cli-plugins: separate hook types from manager and refactor
2026-03-19 14:47:29 +01:00
Sebastiaan van Stijn 4bf4d567bd cli-plugins/hooks: PrintNextSteps: slight cleanup
- skip aec to construct the formatting and use a const instead
- skip fmt.Println and write directly to the writer
- move newlines outside of the "bold" formatting

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-19 12:24:36 +01:00
Sebastiaan van Stijn dd1f7f5856 cli-plugins/hooks: simplify templating formats
This allows for slighly cleaner / more natural placeholders, as it
doesn't require the context (`.`) to be specified;

- `{{command}}` instead of `{{.Command}}` or `{{command .}}`
- `{{flagValue "my-flag"}}` instead of `{{.FlagValue "my-flag"}} or `{{flagValue . "my-flag"}}`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-19 12:24:35 +01:00
Sebastiaan van Stijn 9243240346 cli-plugins/hooks: add commandInfo type for templating
Define a local type for methods to expose to the template, instead of
passing the cobra.Cmd. This avoids templates depending on features
exposed by Cobra that are not part of the contract, and slightly
decouples the templat from the Cobra implementation.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-19 12:24:35 +01:00
Sebastiaan van Stijn 4a1b2ef2c5 cli-plugins/hooks: update godoc
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-19 12:24:35 +01:00
Sebastiaan van Stijn 4142d4026e cli-plugins/hooks: detect if templating is needed
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-19 12:24:35 +01:00
Sebastiaan van Stijn cd053606a6 cli-plugins/hooks: slight tweaks in templates
- use `%q` instead of manually quoting the string
- use `%d` instead of manually converting the number to a string

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-19 12:24:35 +01:00
Sebastiaan van Stijn aadfe6214f cli-plugins/hooks: update tests
- add basic unit-test for the template utilities
- make sure the template parsing tests test both the current
  template produced by the utilities, as well as a fixture
- rewrite the printer test to use fixtures
- use blackbox testing ("hooks_test")

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-19 12:24:34 +01:00
Sebastiaan van Stijn dce201d6ee cli-plugins/hooks: move template utils separate from render code
These utilities are used by CLI-plugins; separate them from the render
code, which is used by teh CLI-plugin manager.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-19 12:24:34 +01:00
Sebastiaan van Stijn e26f94d823 cli-plugins/hooks: add JSON labels, omitzero
Add labels to define the expected casing and don't serialize
empty fields.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-19 12:24:27 +01:00
Sebastiaan van Stijn 0431e4d23c cli-plugins/hooks: rename HookType to ResponseType
Rename the type to match the struct it's used for. Also;

- Fix the type of the NextSteps const
- Don't use iota for values; the ResponseType is used as
  part of the "wire" format, which means that plugins using
  the value can use a different version of the module code;
  using iota increases the risk of (accidentally) changing
  values, which would break the wire format.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-18 12:20:48 +01:00
Sebastiaan van Stijn 607ebfca5d cli-plugins/hooks: rename HookMessage to Response
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-18 12:20:48 +01:00
Sebastiaan van Stijn 60180924e3 cli-plugins/manager: move HookPluginData to hooks.Request
Separate types used by plugins from the manager code.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-18 12:20:48 +01:00
Sebastiaan van Stijn dd91ed3f2d cli-plugins/manager: refactor for easier debugging
Extract the code inside the loop to a closure, so that we can more
easily set up debug-logging.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-18 12:20:42 +01:00
Sebastiaan van Stijn 0501cf8293 cli-plugins/manager: simplify ctx-cancel check
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-18 12:18:46 +01:00
Sebastiaan van Stijn 5343bdc792 cli-plugins/manager: Plugin.RunHook: improve error message
Currently, the error was a plain "exit status 1"; make the error
message more informative if we need it :)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-18 12:18:41 +01:00
Sebastiaan van Stijn 5fb5e0b0da docker stats --all: remove containers when removed
Before this patch, running `docker stats --all` would continue showing
all containers once observed. For example;

    CONTAINER ID   NAME                 CPU %     MEM USAGE / LIMIT     MEM %     NET I/O         BLOCK I/O        PIDS
    f2a785b0cd5f   foo1                 0.00%     8.535MiB / 7.653GiB   0.11%     1.12kB / 126B   0B / 12.3kB      11
    fc191b27517f   foo2                 0.00%     8.531MiB / 7.653GiB   0.11%     998B / 126B     0B / 12.3kB      11
    5040185fba53   foo3                 0.00%     8.578MiB / 7.653GiB   0.11%     872B / 126B     0B / 8.19kB      11

WHen removing `foo2`, the container would continue to be listed:

    CONTAINER ID   NAME                 CPU %     MEM USAGE / LIMIT     MEM %     NET I/O         BLOCK I/O        PIDS
    f2a785b0cd5f   foo1                 0.00%     8.535MiB / 7.653GiB   0.11%     1.12kB / 126B   0B / 12.3kB      11
    fc191b27517f   foo2                 --        -- / --               --        --              --               --
    5040185fba53   foo3                 0.00%     8.578MiB / 7.653GiB   0.11%     872B / 126B     0B / 12.3kB      11

Starting a new `foo2` container would now produce multiple entries:

    CONTAINER ID   NAME                 CPU %     MEM USAGE / LIMIT     MEM %     NET I/O         BLOCK I/O        PIDS
    f2a785b0cd5f   foo1                 0.00%     8.535MiB / 7.653GiB   0.11%     1.25kB / 126B   0B / 12.3kB      11
    fc191b27517f   foo2                 --        -- / --               --        --              --               --
    5040185fba53   foo3                 0.00%     8.578MiB / 7.653GiB   0.11%     998B / 126B     0B / 12.3kB      11
    dba11b9e1ba9   foo2                 0.00%     8.578MiB / 7.653GiB   0.11%     872B / 126B     0B / 8.19kB      11

Repeat that, and the list would continue to grow;

    CONTAINER ID   NAME                 CPU %     MEM USAGE / LIMIT     MEM %     NET I/O         BLOCK I/O        PIDS
    f2a785b0cd5f   foo1                 0.00%     8.535MiB / 7.653GiB   0.11%     1.25kB / 126B   0B / 12.3kB      11
    fc191b27517f   foo2                 --        -- / --               --        --              --               --
    5040185fba53   foo3                 0.00%     8.578MiB / 7.653GiB   0.11%     998B / 126B     0B / 12.3kB      11
    dba11b9e1ba9   foo2                 --        -- / --               --        --              --               --
    193a6dcfaa2d   foo2                 --        -- / --               --        --              --               --
    bf50e58085c6   foo2                 0.00%     8.539MiB / 7.653GiB   0.11%     872B / 126B     0B / 8.19kB      11

After this patch, containers are removed when we observe a `destroy` event;

    CONTAINER ID   NAME                 CPU %     MEM USAGE / LIMIT     MEM %     NET I/O         BLOCK I/O        PIDS
    f2a785b0cd5f   foo1                 0.00%     8.535MiB / 7.653GiB   0.11%     1.5kB / 126B    0B / 12.3kB      11
    5040185fba53   foo3                 0.00%     8.578MiB / 7.653GiB   0.11%     1.25kB / 126B   0B / 12.3kB      11
    bf50e58085c6   foo2                 0.00%     8.539MiB / 7.653GiB   0.11%     872B / 126B     0B / 12.3kB      11

Containers are added when created, so in the example above, the new `foo2`
is added at the end:

    CONTAINER ID   NAME                 CPU %     MEM USAGE / LIMIT     MEM %     NET I/O         BLOCK I/O        PIDS
    f2a785b0cd5f   foo1                 0.00%     8.535MiB / 7.653GiB   0.11%     1.5kB / 126B    0B / 12.3kB      11
    5040185fba53   foo3                 0.00%     8.578MiB / 7.653GiB   0.11%     1.25kB / 126B   0B / 12.3kB      11
    bf50e58085c6   foo2                 0.00%     8.539MiB / 7.653GiB   0.11%     872B / 126B     0B / 12.3kB      11

If a container dies, and `--all` is set, we continue listing it, but stats
are not updated while the container is stopped (we should consider resetting
the stats and show `-- / --` to be more clear that we don't have the container
running).

Here's with `foo3` stopped:

    CONTAINER ID   NAME                 CPU %     MEM USAGE / LIMIT     MEM %     NET I/O         BLOCK I/O        PIDS
    f2a785b0cd5f   foo1                 0.00%     8.535MiB / 7.653GiB   0.11%     1.5kB / 126B    0B / 12.3kB      11
    5040185fba53   foo3                 0.00%     0B / 0B               0.00%     0B / 0B         0B / 0B          0
    bf50e58085c6   foo2                 0.00%     8.539MiB / 7.653GiB   0.11%     872B / 126B     0B / 12.3kB      11

Starting the container continues updating its stats:

    CONTAINER ID   NAME                 CPU %     MEM USAGE / LIMIT     MEM %     NET I/O         BLOCK I/O        PIDS
    f2a785b0cd5f   foo1                 0.00%     8.535MiB / 7.653GiB   0.11%     1.63kB / 126B   0B / 12.3kB      11
    5040185fba53   foo3                 0.00%     8.496MiB / 7.653GiB   0.11%     872B / 126B     0B / 0B          11
    bf50e58085c6   foo2                 0.00%     8.539MiB / 7.653GiB   0.11%     998B / 126B     0B / 12.3kB      11

When running without `--all`, we continue to remove containers as soon as
possible (`die` events), but with `--all`, those events are ignored with
the expectation that the container might come back.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-17 21:05:44 +01:00
Sebastiaan van StijnandGitHub 743c385f78 Merge pull request #6862 from thaJeztah/update_golangci_lint_config
fix linting and update golangci-lint config
2026-03-17 21:05:01 +01:00
Sebastiaan van StijnandGitHub 434193ff78 Merge pull request #6865 from thaJeztah/cleanup_stats
cli/command/container: RunStats: pass ctx to stats event handlers and refractor to DRY
2026-03-17 21:03:38 +01:00
Sebastiaan van StijnandGitHub adc5466cba Merge pull request #6864 from thaJeztah/bump_runewidth
vendor: github.com/mattn/go-runewidth v0.0.21
2026-03-17 21:02:53 +01:00
Sebastiaan van Stijn 560db7d451 vendor: github.com/mattn/go-runewidth v0.0.21
full diff: https://github.com/mattn/go-runewidth/compare/v0.0.20...v0.0.21

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-17 18:36:02 +01:00
Sebastiaan van Stijn dc4abf8b99 golangci-lint: gocheckcompilerdirectives: ignore "//go:fix"
The linter has not been updated yet to recognize this directive.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-17 18:14:57 +01:00
Sebastiaan van Stijn 7f781688ed golangci-lint: remove outdated exclusion
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-17 18:14:54 +01:00
Sebastiaan van Stijn 21293265b1 cli/command/image/build: use t.Chdir() in tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-17 18:14:48 +01:00
Sebastiaan van Stijn 3f51d0a9d2 cli/command/container: RunStats: refactor to DRY
- update setHandler to accept multiple event-types
- pass a logger to the event-handlers with the common fields
  already set.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-17 18:05:33 +01:00
Sebastiaan van Stijn 9645db767a cli/command/container: RunStats: pass ctx to stats event handlers
Wire up the context explicitly instead of capturing it in the closures.
Also pass through the context to `watch` to replace the context.TODO()

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-17 18:05:33 +01:00
Sebastiaan van StijnandGitHub 499a4c50bd Merge pull request #6800 from 4RH1T3CT0R7/master
docker cp: report both content size and transferred size
2026-03-16 17:41:05 +01:00
Paweł GronowskiandGitHub bb0f76343a Merge pull request #6804 from icemc/update-docs-flocker-plugin
Removed EOL Flocker plugin reference from plugin documentations.
2026-03-16 09:40:12 +00:00
4RH1T3CT0R7 2bc66ecbc7 docker cp: report both content size and transferred size
When copying files with `docker cp`, the success message now shows both
the actual content size and the transferred (tar stream) size when they
differ, making it easier to understand compression and overhead:

    Successfully copied 2.01MB (transferred 2.53MB) to ctr:/dir

Extract copySummary helper to keep copyToContainer under the gocyclo
complexity threshold. Add unit tests for copySummary and stdin path.

Signed-off-by: 4RH1T3CT0R7 <iprintercanon@gmail.com>
2026-03-13 21:32:58 +03:00
Sebastiaan van StijnandGitHub 26d4525d46 Merge pull request #6817 from luojiyin1987/fix-plugin-cobra
fix: restore os.Args after plugin completion and fix error return
2026-03-13 15:28:03 +01:00
Ludovic Temgoua AbandaandSebastiaan van Stijn 33790e88d0 docs: use generic myplugin example for plugin documentation
- Removed EOL Flocker plugin reference from plugin documentations.
- docs: use generic myplugin example instead of VolumeDriver

Co-authored-by: Ludovic Temgoua Abanda <abandaludovic500@gmail.com>
Signed-off-by: Ludovic Temgoua Abanda <abandaludovic500@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-13 15:27:02 +01:00
luojiyinandSebastiaan van Stijn 6b1ba1ad84 fix: restore os.Args after plugin completion and fix error return
Signed-off-by: luojiyin <luojiyin@hotmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-13 14:48:45 +01:00
Sebastiaan van StijnandGitHub c3a17b9def Merge pull request #6839 from literally-user/6838-fix-font-representation
scripts/warn-outside-container: fix font representation
2026-03-13 14:36:20 +01:00
Sebastiaan van StijnandGitHub 9ca766c489 Merge pull request #6858 from gounthar/feat/add-riscv64-to-bin-image-cross
Add linux/riscv64 to bin-image-cross release target
2026-03-13 13:13:37 +01:00
Bruno Verachten 300d8231da feat: add linux/riscv64 to bin-image-cross release target
Add linux/riscv64 to the bin-image-cross platforms list so that
official release images include riscv64 binaries.

riscv64 is already present in the _platforms variable (line 38) and
used by the cross, dynbinary-cross, and plugins-cross targets. CI
already builds riscv64 binaries, but they are excluded from the
release image because bin-image-cross has its own platform list.

Closes #6857

Signed-off-by: Bruno Verachten <gounthar@gmail.com>
2026-03-12 10:53:47 +01:00
Sebastiaan van StijnandGitHub 5d8cc2c987 Merge pull request #6854 from thaJeztah/missing_buildtags
cli/command: add missing "go:build" comments
2026-03-10 17:31:48 +01:00
Sebastiaan van StijnandGitHub d0442edbfe Merge pull request #6845 from thaJeztah/cleanup_godoc
cli/config/credentials: ConvertToHostname: update godoc
2026-03-10 17:31:14 +01:00
Sebastiaan van StijnandGitHub 3897d9cc09 Merge pull request #6850 from thaJeztah/update_go1.25.8
update to go1.25.8
2026-03-10 17:30:40 +01:00
Sebastiaan van Stijn 9a471180cb cli/command: add missing "go:build" comments
- commit e8dc2fce32 modernized loops to
  range over int, which requires go1.22 or later.
- commit 85ebca52fd modernized code to
  use stdlib min/max, which requires go1.21 or later.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-10 11:18:58 +01:00
Sebastiaan van StijnandGitHub e2f38c4947 Merge pull request #6848 from docker/dependabot/github_actions/docker/metadata-action-6
build(deps): bump docker/metadata-action from 5 to 6
2026-03-06 16:30:00 +01:00
Sebastiaan van StijnandGitHub 13ec581924 Merge pull request #6847 from docker/dependabot/github_actions/docker/bake-action-7
build(deps): bump docker/bake-action from 6 to 7
2026-03-06 16:29:20 +01:00
Sebastiaan van Stijn f7d83cbae8 update to go1.25.8
go1.25.8 (released 2026-03-05) includes security fixes to the html/template,
net/url, and os packages, as well as bug fixes to the go command, the compiler,
and the os package. See the Go 1.25.8 milestone on our issue tracker for details.

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

---

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

These releases include 5 security fixes following the security policy:

crypto/x509: incorrect enforcement of email constraints

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

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

  This issue only affects Go 1.26.

  Thanks to Jakub Ciolek for reporting this issue.

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

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

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

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

  This issue only affects Go 1.26.

  Thanks to Jakub Ciolek for reporting this issue.

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

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

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

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

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

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

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

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

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

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

- os: FileInfo can escape from a Root

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

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

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

  The FileInfo is now populated using fstatat.

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

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-06 13:52:18 +01:00
dependabot[bot]andGitHub 39d676c72d build(deps): bump docker/metadata-action from 5 to 6
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-06 08:42:45 +00:00
dependabot[bot]andGitHub 6453c4c3a7 build(deps): bump docker/bake-action from 6 to 7
Bumps [docker/bake-action](https://github.com/docker/bake-action) from 6 to 7.
- [Release notes](https://github.com/docker/bake-action/releases)
- [Commits](https://github.com/docker/bake-action/compare/v6...v7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-06 08:42:41 +00:00
Sebastiaan van Stijn eef3c957be cli/config/credentials: ConvertToHostname: update godoc
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-05 19:02:03 +01:00
Davlat Davydov 71db1520de scripts/warn-outside-container: fix font representation
Signed-off-by: Davlat Davydov <literally_user@hotmail.com>

fix CI

review changes
2026-03-05 19:34:01 +03:00
403 changed files with 11987 additions and 3188 deletions
+2
View File
@@ -7,3 +7,5 @@ updates:
labels:
- "area/testing"
- "status/2-code-review"
cooldown:
default-days: 7
+12 -12
View File
@@ -35,7 +35,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Create matrix
id: platforms
@@ -63,10 +63,10 @@ jobs:
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Build
uses: docker/bake-action@v6
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
with:
targets: ${{ matrix.target }}
set: |
@@ -88,7 +88,7 @@ jobs:
fi
-
name: Upload artifacts
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ env.ARTIFACT_NAME }}
path: /tmp/out/*
@@ -101,20 +101,20 @@ jobs:
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_CLIBIN_USERNAME }}
password: ${{ secrets.DOCKERHUB_CLIBIN_TOKEN }}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
with:
images: dockereng/cli-bin
tags: |
@@ -125,7 +125,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
-
name: Build and push image
uses: docker/bake-action@v6
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
with:
files: |
./docker-bake.hcl
@@ -143,7 +143,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Create matrix
id: platforms
@@ -165,10 +165,10 @@ jobs:
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Build
uses: docker/bake-action@v6
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
with:
targets: plugins-cross
set: |
+7 -6
View File
@@ -46,7 +46,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 2
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
@@ -61,19 +61,20 @@ jobs:
ln -s vendor.sum go.sum
-
name: Update Go
uses: actions/setup-go@v6
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version: "1.25.7"
go-version: "1.26.2"
cache: false
-
name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
languages: go
-
name: Autobuild
uses: github/codeql-action/autobuild@v4
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
category: "/language:go"
+3 -3
View File
@@ -44,7 +44,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Update daemon.json
run: |
@@ -63,7 +63,7 @@ jobs:
docker info
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Run ${{ matrix.target }}
run: |
@@ -74,7 +74,7 @@ jobs:
TESTFLAGS: -coverprofile=/tmp/coverage/coverage.txt
-
name: Send to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
with:
files: ./build/coverage/coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
+8 -7
View File
@@ -30,15 +30,15 @@ jobs:
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Test
uses: docker/bake-action@v6
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
with:
targets: test-coverage
-
name: Send to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
with:
files: ./build/coverage/coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
@@ -60,14 +60,15 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
path: ${{ env.GOPATH }}/src/github.com/docker/cli
-
name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version: "1.25.7"
go-version: "1.26.2"
cache: false
-
name: Test
run: |
@@ -80,7 +81,7 @@ jobs:
shell: bash
-
name: Send to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
with:
files: /tmp/coverage.txt
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
+35
View File
@@ -0,0 +1,35 @@
name: validate-milestone
permissions:
contents: read
on:
pull_request:
types: [opened, synchronize, milestoned, demilestoned, edited]
jobs:
validate-milestone:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
sparse-checkout: VERSION
- name: Validate milestone matches VERSION
run: |
expected=$(cat VERSION)
milestone="${{ github.event.pull_request.milestone.title }}"
if [[ -z "$milestone" ]]; then
echo "::error::PR must have a milestone set (expected: $expected)"
exit 1
fi
if [[ "$milestone" != "$expected" ]]; then
echo "::error::Milestone '$milestone' does not match VERSION '$expected'"
exit 1
fi
echo "Milestone: $milestone ✓"
+27 -3
View File
@@ -38,7 +38,7 @@ jobs:
steps:
-
name: Run
uses: docker/bake-action@v6
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
with:
targets: ${{ matrix.target }}
@@ -48,7 +48,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Generate
shell: 'script --return --quiet --command "bash {0}"'
@@ -74,9 +74,33 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Run
shell: 'script --return --quiet --command "bash {0}"'
run: |
make -f docker.Makefile ${{ matrix.target }}
validate-gocompat:
runs-on: ubuntu-24.04
env:
GOPATH: ${{ github.workspace }}
GO111MODULE: off
steps:
-
name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
path: src/github.com/docker/cli
-
name: Set up Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version: "1.26.2"
cache: false
-
name: Run gocompat check
shell: 'script --return --quiet --command "bash {0}"'
working-directory: ${{ github.workspace }}/src/github.com/docker/cli
run: |
make -C ./internal/gocompat verify
+11 -12
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.7"
go: "1.26.2"
timeout: 5m
@@ -110,8 +110,15 @@ linters:
excludes:
- G104 # G104: Errors unhandled; (TODO: reduce unhandled errors, or explicitly ignore)
- G115 # G115: integer overflow conversion; (TODO: verify these: https://github.com/docker/cli/issues/5584)
- G117 # G117: Exported struct field matches secret pattern (false positives for legitimate field names)
- G118 # G118: Goroutine uses context.Background/TODO while request-scoped context is available (TODO: evaluate these)
- G122 # G122: Filesystem operation in filepath.Walk/WalkDir callback uses race-prone path (TODO: evaluate these)
- G306 # G306: Expect WriteFile permissions to be 0600 or less (too restrictive; also flags "0o644" permissions)
- G307 # G307: Deferring unsafe method "*os.File" on type "Close" (also EXC0008); (TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close")
- G702 # G702: Command injection via taint analysis (TODO: evaluate these)
- G703 # G703: Path traversal via taint analysis (TODO: evaluate these)
- G704 # G704: SSRF via taint analysis (TODO: evaluate these)
- G705 # G705: XSS via taint analysis (TODO: evaluate these)
govet:
enable:
@@ -158,11 +165,6 @@ linters:
- name: use-any # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#use-any
- name: use-errors-new # https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md#use-errors-new
usetesting:
os-chdir: false # FIXME(thaJeztah): Disable `os.Chdir()` detections; should be automatically disabled on Go < 1.24; see https://github.com/docker/cli/pull/5835#issuecomment-2665302478
context-background: false # FIXME(thaJeztah): Disable `context.Background()` detections; should be automatically disabled on Go < 1.24; see https://github.com/docker/cli/pull/5835#issuecomment-2665302478
context-todo: false # FIXME(thaJeztah): Disable `context.TODO()` detections; should be automatically disabled on Go < 1.24; see https://github.com/docker/cli/pull/5835#issuecomment-2665302478
exclusions:
# We prefer to use an "linters.exclusions.rules" so that new "default" exclusions are not
# automatically inherited. We can decide whether or not to follow upstream
@@ -225,13 +227,10 @@ linters:
linters:
- staticcheck
# Ignore deprecation linting for cli/command/stack/*.
#
# FIXME(thaJeztah): remove exception once these functions are un-exported or internal; see https://github.com/docker/cli/pull/6389
- text: '^(SA1019): '
path: "cli/command/stack"
# TODO(thaJeztah): remove once https://github.com/leighmcculloch/gocheckcompilerdirectives/issues/7 is fixed.
- text: "compiler directive unrecognized: //go:fix"
linters:
- staticcheck
- gocheckcompilerdirectives
# Log a warning if an exclusion rule is unused.
# Default: false
+1
View File
@@ -354,6 +354,7 @@ Lorenzo Fontana <lo@linux.com> <fontanalorenzo@me.com>
Louis Opter <kalessin@kalessin.fr>
Louis Opter <kalessin@kalessin.fr> <louis@dotcloud.com>
Lovekesh Kumar <lovekesh.kumar@rtcamp.com>
Luo Jiyin <luojiyin@hotmail.com>
Luca Favatella <luca.favatella@erlang-solutions.com> <lucafavatella@users.noreply.github.com>
Luke Marsden <me@lukemarsden.net> <luke@digital-crocus.com>
Lyn <energylyn@zju.edu.cn>
+16 -1
View File
@@ -2,6 +2,7 @@
# This file lists all contributors to the repository.
# See scripts/docs/generate-authors.sh to make modifications.
4RH1T3CT0R7 <iprintercanon@gmail.com>
A. Lester Buck III <github-reg@nbolt.com>
Aanand Prasad <aanand.prasad@gmail.com>
Aaron L. Xu <liker.xu@foxmail.com>
@@ -42,6 +43,7 @@ Alexander Larsson <alexl@redhat.com>
Alexander Morozov <lk4d4math@gmail.com>
Alexander Ryabov <i@sepa.spb.ru>
Alexandre González <agonzalezro@gmail.com>
Alexandre Vallières-Lagacé <alexandre.valliereslagace@docker.com>
Alexey Igrychev <alexey.igrychev@flant.com>
Alexis Couvreur <alexiscouvreur.pro@gmail.com>
Alfred Landrum <alfred.landrum@docker.com>
@@ -64,6 +66,7 @@ Andres G. Aragoneses <knocte@gmail.com>
Andres Leon Rangel <aleon1220@gmail.com>
Andrew France <andrew@avito.co.uk>
Andrew He <he.andrew.mail@gmail.com>
Andrew Hopp <andrew.hopp@me.com>
Andrew Hsu <andrewhsu@docker.com>
Andrew Macpherson <hopscotch23@gmail.com>
Andrew McDonnell <bugs@andrewmcdonnell.net>
@@ -127,6 +130,7 @@ Brian Goff <cpuguy83@gmail.com>
Brian Tracy <brian.tracy33@gmail.com>
Brian Wieder <brian@4wieders.com>
Bruno Sousa <bruno.sousa@docker.com>
Bruno Verachten <gounthar@gmail.com>
Bryan Bess <squarejaw@bsbess.com>
Bryan Boreham <bjboreham@gmail.com>
Bryan Murphy <bmurphy1976@gmail.com>
@@ -178,6 +182,7 @@ Christopher Svensson <stoffus@stoffus.com>
Christy Norman <christy@linux.vnet.ibm.com>
Chun Chen <ramichen@tencent.com>
Clinton Kitson <clintonskitson@gmail.com>
Codex <codex@openai.com>
Coenraad Loubser <coenraad@wish.org.za>
Colin Hebert <hebert.colin@gmail.com>
Collin Guarino <collin.guarino@gmail.com>
@@ -234,6 +239,7 @@ David Sheets <dsheets@docker.com>
David Williamson <david.williamson@docker.com>
David Xia <dxia@spotify.com>
David Young <yangboh@cn.ibm.com>
Davlat Davydov <literally_user@hotmail.com>
Deng Guangxing <dengguangxing@huawei.com>
Denis Defreyne <denis@soundcloud.com>
Denis Gladkikh <denis@gladkikh.email>
@@ -241,6 +247,7 @@ Denis Ollier <larchunix@users.noreply.github.com>
Dennis Docter <dennis@d23.nl>
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Derek McGowan <derek@mcg.dev>
Derek Misler <derek.misler@docker.com>
Des Preston <despreston@gmail.com>
Deshi Xiao <dxiao@redhat.com>
Dharmit Shah <shahdharmit@gmail.com>
@@ -260,6 +267,7 @@ Dominik Braun <dominik.braun@nbsp.de>
Don Kjer <don.kjer@gmail.com>
Dong Chen <dongluo.chen@docker.com>
DongGeon Lee <secmatth1996@gmail.com>
Dorin Geman <dorin.geman@docker.com>
Doug Davis <dug@us.ibm.com>
Drew Erny <derny@mirantis.com>
Ed Costello <epc@epcostello.com>
@@ -358,7 +366,7 @@ Hugo Gabriel Eyherabide <hugogabriel.eyherabide@gmail.com>
huqun <huqun@zju.edu.cn>
Huu Nguyen <huu@prismskylabs.com>
Hyzhou Zhy <hyzhou.zhy@alibaba-inc.com>
Iain MacDonald <IJMacD@gmail.com>
Iain MacDonald <ijmacd@gmail.com>
Iain Samuel McLean Elder <iain@isme.es>
Ian Campbell <ian.campbell@docker.com>
Ian Philpot <ian.philpot@microsoft.com>
@@ -471,6 +479,7 @@ Justyn Temme <justyntemme@gmail.com>
Jyrki Puttonen <jyrkiput@gmail.com>
Jérémie Drouet <jeremie.drouet@gmail.com>
Jérôme Petazzoni <jerome.petazzoni@docker.com>
Jörg Sommer <joerg@jo-so.de>
Jörg Thalheim <joerg@higgsboson.tk>
Kai Blin <kai@samba.org>
Kai Qiang Wu (Kennan) <wkq5325@gmail.com>
@@ -539,10 +548,12 @@ Lovekesh Kumar <lovekesh.kumar@rtcamp.com>
Luca Favatella <luca.favatella@erlang-solutions.com>
Luca Marturana <lucamarturana@gmail.com>
Lucas Chan <lucas-github@lucaschan.com>
Ludovic Temgoua Abanda <abandaludovic500@gmail.com>
Luis Henrique Mulinari <luis.mulinari@gmail.com>
Luka Hartwig <mail@lukahartwig.de>
Lukas Heeren <lukas-heeren@hotmail.com>
Lukasz Zajaczkowski <Lukasz.Zajaczkowski@ts.fujitsu.com>
Luo Jiyin <luojiyin@hotmail.com>
Lydell Manganti <LydellManganti@users.noreply.github.com>
Lénaïc Huard <lhuard@amadeus.com>
Ma Shimiao <mashimiao.fnst@cn.fujitsu.com>
@@ -603,6 +614,7 @@ Michael Spetsiotis <michael_spets@hotmail.com>
Michael Steinert <mike.steinert@gmail.com>
Michael Tews <michael@tews.dev>
Michael West <mwest@mdsol.com>
Michael Zampani <michael.zampani@docker.com>
Michal Minář <miminar@redhat.com>
Michał Czeraszkiewicz <czerasz@gmail.com>
Miguel Angel Alvarez Cabrerizo <doncicuto@gmail.com>
@@ -617,6 +629,7 @@ Mike Goelzer <mike.goelzer@docker.com>
Mike MacCana <mike.maccana@gmail.com>
mikelinjie <294893458@qq.com>
Mikhail Vasin <vasin@cloud-tv.ru>
Milas Bowman <milas.bowman@docker.com>
Milind Chawre <milindchawre@gmail.com>
Mindaugas Rukas <momomg@gmail.com>
Miroslav Gula <miroslav.gula@naytrolabs.com>
@@ -887,6 +900,7 @@ Vincent Batts <vbatts@redhat.com>
Vincent Bernat <Vincent.Bernat@exoscale.ch>
Vincent Demeester <vincent.demeester@docker.com>
Vincent Woo <me@vincentwoo.com>
Vineet Kumar <vineetkumar17112004@gmail.com>
Vishnu Kannan <vishnuk@google.com>
Vivek Goyal <vgoyal@redhat.com>
Wang Jie <wangjie5@chinaskycloud.com>
@@ -916,6 +930,7 @@ Yanqiang Miao <miao.yanqiang@zte.com.cn>
Yassine Tijani <yasstij11@gmail.com>
Yi EungJun <eungjun.yi@navercorp.com>
Ying Li <ying.li@docker.com>
Yoan Wainmann <thebook90yw@gmail.com>
Yong Tang <yong.tang.github@outlook.com>
Yosef Fertel <yfertel@gmail.com>
Yu Peng <yu.peng36@zte.com.cn>
+3 -3
View File
@@ -8,7 +8,7 @@ ARG BASE_VARIANT=alpine
ARG ALPINE_VERSION=3.23
ARG BASE_DEBIAN_DISTRO=bookworm
ARG GO_VERSION=1.25.7
ARG GO_VERSION=1.26.2
# 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.
@@ -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.31.1
ARG BUILDX_VERSION=0.33.0
# 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=v5.1.0
ARG COMPOSE_VERSION=v5.1.3
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
+1 -1
View File
@@ -1 +1 @@
29.0.0-dev
29.4.1
+85
View File
@@ -0,0 +1,85 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.25
// Package hooks defines the contract between the Docker CLI and CLI plugin hook
// implementations.
//
// # Audience
//
// This package is intended to be imported by CLI plugin implementations that
// implement a "hooks" subcommand, and by the Docker CLI when invoking those
// hooks.
//
// # Contract and wire format
//
// Hook inputs (see [Request]) are serialized as JSON and passed to the plugin hook
// subcommand (currently as a command-line argument). Hook outputs are emitted by
// the plugin as JSON (see [Response]).
//
// # Stability
//
// The types that represent the hook contract ([Request], [Response] and related
// constants) are considered part of Docker CLI's public Go API.
// Fields and values may be extended in a backwards-compatible way (for example,
// adding new fields), but existing fields and their meaning should remain stable.
// Plugins should ignore unknown fields and unknown hook types to remain
// forwards-compatible.
package hooks
// ResponseType is the type of response from the plugin.
type ResponseType int
const (
NextSteps ResponseType = 0
)
// Request is the type representing the information
// that plugins declaring support for hooks get passed when
// being invoked following a CLI command execution.
type Request struct {
// RootCmd is a string representing the matching hook configuration
// which is currently being invoked. If a hook for "docker context"
// is configured and the user executes "docker context ls", the plugin
// is invoked with "context".
RootCmd string `json:"RootCmd,omitzero"`
// Flags contains flags that were set on the command for which the
// hook was invoked. It uses flag names as key, with leading hyphens
// removed ("--flag" and "-flag" are included as "flag" and "f").
//
// Flag values are not included and are set to an empty string,
// except for boolean flags known to the CLI itself, for which
// the value is either "true", or "false".
//
// Plugins can use this information to adjust their [Response]
// based on whether the command triggering the hook was invoked
// with.
Flags map[string]string `json:"Flags,omitzero"`
// CommandError is a string containing the error output (if any)
// of the command for which the hook was invoked.
CommandError string `json:"CommandError,omitzero"`
}
// Response represents a plugin hook response. Plugins
// declaring support for CLI hooks need to print a JSON
// representation of this type when their hook subcommand
// is invoked.
type Response struct {
Type ResponseType `json:"Type"`
Template string `json:"Template,omitzero"`
}
// HookType is the type of response from the plugin.
//
// Deprecated: use [ResponseType] instead.
//
//go:fix inline
type HookType = ResponseType
// HookMessage represents a plugin hook response.
//
// Deprecated: use [Response] instead.
//
//go:fix inline
type HookMessage = Response
+75
View File
@@ -0,0 +1,75 @@
package hooks
import (
"fmt"
)
const (
hookTemplateCommandName = `{{command}}`
hookTemplateFlagValue = `{{flagValue %q}}`
hookTemplateArg = `{{argValue %d}}`
)
// TemplateReplaceSubcommandName returns a hook template string
// that will be replaced by the CLI subcommand being executed
//
// Example:
//
// Response{
// Type: NextSteps,
// Template: "you ran the subcommand: " + TemplateReplaceSubcommandName(),
// }
//
// When being executed after the command:
//
// docker run --name "my-container" alpine
//
// It results in the message:
//
// you ran the subcommand: run
func TemplateReplaceSubcommandName() string {
return hookTemplateCommandName
}
// TemplateReplaceFlagValue returns a hook template string that will be
// replaced with the flags value when printed by the CLI.
//
// Example:
//
// Response{
// Type: NextSteps,
// Template: "you ran a container named: " + TemplateReplaceFlagValue("name"),
// }
//
// when executed after the command:
//
// docker run --name "my-container" alpine
//
// it results in the message:
//
// you ran a container named: my-container
func TemplateReplaceFlagValue(flag string) string {
return fmt.Sprintf(hookTemplateFlagValue, flag)
}
// TemplateReplaceArg takes an index i and returns a hook
// template string that the CLI will replace the template with
// the ith argument after processing the passed flags.
//
// Example:
//
// Response{
// Type: NextSteps,
// Template: "run this image with `docker run " + TemplateReplaceArg(0) + "`",
// }
//
// when being executed after the command:
//
// docker pull alpine
//
// It results in the message:
//
// Run this image with `docker run alpine`
func TemplateReplaceArg(i int) string {
return fmt.Sprintf(hookTemplateArg, i)
}
+50
View File
@@ -0,0 +1,50 @@
package hooks_test
import (
"testing"
"github.com/docker/cli/cli-plugins/hooks"
)
func TestTemplateHelpers(t *testing.T) {
tests := []struct {
doc string
got func() string
want string
}{
{
doc: "subcommand name",
got: hooks.TemplateReplaceSubcommandName,
want: `{{command}}`,
},
{
doc: "flag value",
got: func() string {
return hooks.TemplateReplaceFlagValue("name")
},
want: `{{flagValue "name"}}`,
},
{
doc: "arg",
got: func() string {
return hooks.TemplateReplaceArg(0)
},
want: `{{argValue 0}}`,
},
{
doc: "arg",
got: func() string {
return hooks.TemplateReplaceArg(3)
},
want: `{{argValue 3}}`,
},
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
if got := tc.got(); got != tc.want {
t.Fatalf("expected %q, got %q", tc.want, got)
}
})
}
}
+12 -7
View File
@@ -1,18 +1,23 @@
package hooks
import (
"fmt"
"io"
import "io"
"github.com/morikuni/aec"
const (
whatsNext = "\n\033[1mWhat's next:\033[0m\n"
indent = " "
)
// PrintNextSteps renders list of [NextSteps] messages and writes them
// to out. It is a no-op if messages is empty.
func PrintNextSteps(out io.Writer, messages []string) {
if len(messages) == 0 {
return
}
_, _ = fmt.Fprintln(out, aec.Bold.Apply("\nWhat's next:"))
for _, n := range messages {
_, _ = fmt.Fprintln(out, " ", n)
_, _ = io.WriteString(out, whatsNext)
for _, msg := range messages {
_, _ = io.WriteString(out, indent)
_, _ = io.WriteString(out, msg)
_, _ = io.WriteString(out, "\n")
}
}
+19 -12
View File
@@ -1,38 +1,45 @@
package hooks
package hooks_test
import (
"bytes"
"strings"
"testing"
"github.com/morikuni/aec"
"github.com/docker/cli/cli-plugins/hooks"
"gotest.tools/v3/assert"
)
func TestPrintHookMessages(t *testing.T) {
testCases := []struct {
const header = "\n\x1b[1mWhat's next:\x1b[0m\n"
tests := []struct {
doc string
messages []string
expectedOutput string
}{
{
messages: []string{},
doc: "no messages",
messages: nil,
expectedOutput: "",
},
{
doc: "single message",
messages: []string{"Bork!"},
expectedOutput: aec.Bold.Apply("\nWhat's next:") + "\n" +
expectedOutput: header +
" Bork!\n",
},
{
doc: "multiple messages",
messages: []string{"Foo", "bar"},
expectedOutput: aec.Bold.Apply("\nWhat's next:") + "\n" +
expectedOutput: header +
" Foo\n" +
" bar\n",
},
}
for _, tc := range testCases {
w := bytes.Buffer{}
PrintNextSteps(&w, tc.messages)
assert.Equal(t, w.String(), tc.expectedOutput)
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
var w strings.Builder
hooks.PrintNextSteps(&w, tc.messages)
assert.Equal(t, w.String(), tc.expectedOutput)
})
}
}
+65 -91
View File
@@ -1,116 +1,90 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.25
package hooks
import (
"bytes"
"errors"
"fmt"
"strconv"
"strings"
"text/template"
"github.com/spf13/cobra"
)
type HookType int
const (
NextSteps = iota
)
// HookMessage represents a plugin hook response. Plugins
// declaring support for CLI hooks need to print a json
// representation of this type when their hook subcommand
// is invoked.
type HookMessage struct {
Type HookType
Template string
}
// TemplateReplaceSubcommandName returns a hook template string
// that will be replaced by the CLI subcommand being executed
//
// Example:
//
// "you ran the subcommand: " + TemplateReplaceSubcommandName()
//
// when being executed after the command:
// `docker run --name "my-container" alpine`
// will result in the message:
// `you ran the subcommand: run`
func TemplateReplaceSubcommandName() string {
return hookTemplateCommandName
}
// TemplateReplaceFlagValue returns a hook template string
// that will be replaced by the flags value.
//
// Example:
//
// "you ran a container named: " + TemplateReplaceFlagValue("name")
//
// when being executed after the command:
// `docker run --name "my-container" alpine`
// will result in the message:
// `you ran a container named: my-container`
func TemplateReplaceFlagValue(flag string) string {
return fmt.Sprintf(hookTemplateFlagValue, flag)
}
// TemplateReplaceArg takes an index i and returns a hook
// template string that the CLI will replace the template with
// the ith argument, after processing the passed flags.
//
// Example:
//
// "run this image with `docker run " + TemplateReplaceArg(0) + "`"
//
// when being executed after the command:
// `docker pull alpine`
// will result in the message:
// "Run this image with `docker run alpine`"
func TemplateReplaceArg(i int) string {
return fmt.Sprintf(hookTemplateArg, strconv.Itoa(i))
}
func ParseTemplate(hookTemplate string, cmd *cobra.Command) ([]string, error) {
tmpl := template.New("").Funcs(commandFunctions)
tmpl, err := tmpl.Parse(hookTemplate)
if err != nil {
return nil, err
out := hookTemplate
if strings.Contains(hookTemplate, "{{") {
// Message may be a template.
msgContext := commandInfo{cmd: cmd}
tmpl, err := template.New("").Funcs(template.FuncMap{
"command": msgContext.command,
"flagValue": msgContext.flagValue,
"argValue": msgContext.argValue,
// kept for backward-compatibility with old templates.
"flag": func(_ any, flagName string) (string, error) { return msgContext.flagValue(flagName) },
"arg": func(_ any, i int) (string, error) { return msgContext.argValue(i) },
}).Parse(hookTemplate)
if err != nil {
return nil, err
}
var b bytes.Buffer
err = tmpl.Execute(&b, msgContext)
if err != nil {
return nil, err
}
out = b.String()
}
b := bytes.Buffer{}
err = tmpl.Execute(&b, cmd)
if err != nil {
return nil, err
}
return strings.Split(b.String(), "\n"), nil
return strings.Split(out, "\n"), nil
}
var ErrHookTemplateParse = errors.New("failed to parse hook template")
const (
hookTemplateCommandName = "{{.Name}}"
hookTemplateFlagValue = `{{flag . "%s"}}`
hookTemplateArg = "{{arg . %s}}"
)
var commandFunctions = template.FuncMap{
"flag": getFlagValue,
"arg": getArgValue,
// commandInfo provides info about the command for which the hook was invoked.
// It is used for templated hook-messages.
type commandInfo struct {
cmd *cobra.Command
}
func getFlagValue(cmd *cobra.Command, flag string) (string, error) {
cmdFlag := cmd.Flag(flag)
if cmdFlag == nil {
return "", ErrHookTemplateParse
// Name returns the name of the (sub)command for which the hook was invoked.
//
// It's used for backward-compatibility with old templates.
func (c commandInfo) Name() string {
return c.command()
}
// command returns the name of the (sub)command for which the hook was invoked.
func (c commandInfo) command() string {
if c.cmd == nil {
return ""
}
return cmdFlag.Value.String(), nil
return c.cmd.Name()
}
func getArgValue(cmd *cobra.Command, i int) (string, error) {
flags := cmd.Flags()
if flags == nil {
return "", ErrHookTemplateParse
// flagValue returns the value that was set for the given flag when the hook was invoked.
func (c commandInfo) flagValue(flagName string) (string, error) {
if c.cmd == nil {
return "", fmt.Errorf("%w: flagValue: cmd is nil", ErrHookTemplateParse)
}
return flags.Arg(i), nil
f := c.cmd.Flag(flagName)
if f == nil {
return "", fmt.Errorf("%w: flagValue: no flags found", ErrHookTemplateParse)
}
return f.Value.String(), nil
}
// argValue returns the value of the nth argument.
func (c commandInfo) argValue(n int) (string, error) {
if c.cmd == nil {
return "", fmt.Errorf("%w: arg: cmd is nil", ErrHookTemplateParse)
}
flags := c.cmd.Flags()
v := flags.Arg(n)
if v == "" && n >= flags.NArg() {
return "", fmt.Errorf("%w: arg: %dth argument not set", ErrHookTemplateParse, n)
}
return v, nil
}
+64 -25
View File
@@ -1,43 +1,67 @@
package hooks
package hooks_test
import (
"testing"
"github.com/docker/cli/cli-plugins/hooks"
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
)
// TestParseTemplate tests parsing templates as returned by plugins.
//
// It uses fixed string fixtures to lock in compatibility with existing
// plugin templates, so older formats continue to work even if we add new
// template forms.
//
// For helper-backed cases, it also verifies that templates produced by the
// current TemplateReplace* helpers parse to the same output. This lets us
// evolve the emitted template format without breaking older plugins.
func TestParseTemplate(t *testing.T) {
type testFlag struct {
name string
value string
}
testCases := []struct {
template string
tests := []struct {
doc string
template string // compatibility fixture; keep even if helpers emit a newer form
templateFunc func() string
flags []testFlag
args []string
expectedOutput []string
}{
{
doc: "empty template",
template: "",
expectedOutput: []string{""},
},
{
doc: "plain message",
template: "a plain template message",
expectedOutput: []string{"a plain template message"},
},
{
template: TemplateReplaceFlagValue("tag"),
doc: "subcommand name",
template: "hello {{.Name}}", // NOTE: fixture; do not modify without considering plugin compatibility
templateFunc: func() string { return "hello " + hooks.TemplateReplaceSubcommandName() },
expectedOutput: []string{"hello pull"},
},
{
doc: "single flag",
template: `{{flag . "tag"}}`, // NOTE: fixture; do not modify without considering plugin compatibility
templateFunc: func() string { return hooks.TemplateReplaceFlagValue("tag") },
flags: []testFlag{
{
name: "tag",
value: "my-tag",
},
{name: "tag", value: "my-tag"},
},
expectedOutput: []string{"my-tag"},
},
{
template: TemplateReplaceFlagValue("test-one") + " " + TemplateReplaceFlagValue("test2"),
doc: "multiple flags",
template: `{{flag . "test-one"}} {{flag . "test2"}}`, // NOTE: fixture; do not modify without considering plugin compatibility
templateFunc: func() string {
return hooks.TemplateReplaceFlagValue("test-one") + " " + hooks.TemplateReplaceFlagValue("test2")
},
flags: []testFlag{
{
name: "test-one",
@@ -51,36 +75,51 @@ func TestParseTemplate(t *testing.T) {
expectedOutput: []string{"value value2"},
},
{
template: TemplateReplaceArg(0) + " " + TemplateReplaceArg(1),
doc: "multiple args",
template: `{{arg . 0}} {{arg . 1}}`, // NOTE: fixture; do not modify without considering plugin compatibility
templateFunc: func() string { return hooks.TemplateReplaceArg(0) + " " + hooks.TemplateReplaceArg(1) },
args: []string{"zero", "one"},
expectedOutput: []string{"zero one"},
},
{
template: "You just pulled " + TemplateReplaceArg(0),
doc: "arg in sentence",
template: "You just pulled {{arg . 0}}", // NOTE: fixture; do not modify without considering plugin compatibility
templateFunc: func() string { return "You just pulled " + hooks.TemplateReplaceArg(0) },
args: []string{"alpine"},
expectedOutput: []string{"You just pulled alpine"},
},
{
doc: "multiline output",
template: "one line\nanother line!",
expectedOutput: []string{"one line", "another line!"},
},
}
for _, tc := range testCases {
testCmd := &cobra.Command{
Use: "pull",
Args: cobra.ExactArgs(len(tc.args)),
}
for _, f := range tc.flags {
_ = testCmd.Flags().String(f.name, "", "")
err := testCmd.Flag(f.name).Value.Set(f.value)
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
testCmd := &cobra.Command{
Use: "pull",
Args: cobra.ExactArgs(len(tc.args)),
}
for _, f := range tc.flags {
_ = testCmd.Flags().String(f.name, "", "")
err := testCmd.Flag(f.name).Value.Set(f.value)
assert.NilError(t, err)
}
err := testCmd.Flags().Parse(tc.args)
assert.NilError(t, err)
}
err := testCmd.Flags().Parse(tc.args)
assert.NilError(t, err)
out, err := ParseTemplate(tc.template, testCmd)
assert.NilError(t, err)
assert.DeepEqual(t, out, tc.expectedOutput)
// Validate using fixtures.
out, err := hooks.ParseTemplate(tc.template, testCmd)
assert.NilError(t, err)
assert.DeepEqual(t, out, tc.expectedOutput)
if tc.templateFunc != nil {
// Validate using the current template function equivalent.
out, err = hooks.ParseTemplate(tc.templateFunc(), testCmd)
assert.NilError(t, err)
assert.DeepEqual(t, out, tc.expectedOutput)
}
})
}
}
+5 -2
View File
@@ -45,8 +45,7 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
RunE: func(cmd *cobra.Command, args []string) error {
flags := rootCmd.PersistentFlags()
flags.SetOutput(nil)
perr := flags.Parse(args)
if perr != nil {
if err := flags.Parse(args); err != nil {
return err
}
if flags.Changed("help") {
@@ -60,7 +59,11 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
cargs := []string{p.Path, cobra.ShellCompRequestCmd, p.Name} //nolint:prealloc // no need to over-complicate things.
cargs = append(cargs, args...)
cargs = append(cargs, toComplete)
origArgs := os.Args
os.Args = cargs
defer func() {
os.Args = origArgs
}()
runCommand, runErr := PluginRunCommand(dockerCLI, p.Name, cmd)
if runErr != nil {
return nil, cobra.ShellCompDirectiveError
+59
View File
@@ -1,8 +1,12 @@
package manager
import (
"os"
"path/filepath"
"sync"
"testing"
"github.com/docker/cli/internal/test"
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
)
@@ -24,3 +28,58 @@ func TestPluginResourceAttributesEnvvar(t *testing.T) {
env = appendPluginResourceAttributesEnvvar(nil, cmd, Plugin{Name: "compose"})
assert.DeepEqual(t, []string{"OTEL_RESOURCE_ATTRIBUTES=a.b.c=foo,docker.cli.cobra.command_path=docker%20compose"}, env)
}
func TestPluginStubRunEReturnsParseError(t *testing.T) {
cmd, err := preparePluginStubCommand(t)
assert.NilError(t, err)
err = cmd.RunE(cmd, []string{"--definitely-not-a-real-flag"})
assert.ErrorContains(t, err, "unknown flag: --definitely-not-a-real-flag")
}
func TestPluginStubCompletionRestoresOSArgs(t *testing.T) {
cmd, err := preparePluginStubCommand(t)
assert.NilError(t, err)
savedArgs := os.Args
t.Cleanup(func() { os.Args = savedArgs })
originalArgs := []string{"docker", "image", "ls"}
os.Args = append([]string(nil), originalArgs...)
_, directive := cmd.ValidArgsFunction(cmd, []string{"--all"}, "alp")
assert.Equal(t, directive, cobra.ShellCompDirectiveError)
assert.DeepEqual(t, os.Args, originalArgs)
}
func preparePluginStubCommand(t *testing.T) (*cobra.Command, error) {
t.Helper()
pluginCommandStubsOnce = sync.Once{}
tmpDir := t.TempDir()
const cliPlugin = `#!/bin/sh
printf '%s' '{"SchemaVersion":"0.1.0"}'
`
if err := os.WriteFile(filepath.Join(tmpDir, "docker-testplugin"), []byte(cliPlugin), 0o777); err != nil {
return nil, err
}
cli := test.NewFakeCli(nil)
cli.ConfigFile().CLIPluginsExtraDirs = []string{tmpDir}
root := &cobra.Command{Use: "docker"}
root.PersistentFlags().Bool("debug", false, "")
if err := AddPluginCommandStubs(cli, root); err != nil {
return nil, err
}
cmd, _, err := root.Find([]string{"testplugin"})
if err != nil {
return nil, err
}
if cmd == nil {
return nil, os.ErrNotExist
}
return cmd, nil
}
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package manager
+45 -31
View File
@@ -1,11 +1,14 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package manager
import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"github.com/docker/cli/cli-plugins/hooks"
@@ -19,15 +22,11 @@ import (
// HookPluginData is the type representing the information
// that plugins declaring support for hooks get passed when
// being invoked following a CLI command execution.
type HookPluginData struct {
// RootCmd is a string representing the matching hook configuration
// which is currently being invoked. If a hook for `docker context` is
// configured and the user executes `docker context ls`, the plugin will
// be invoked with `context`.
RootCmd string
Flags map[string]string
CommandError string
}
//
// Deprecated: use [hooks.Request] instead.
//
//go:fix inline
type HookPluginData = hooks.Request
// RunCLICommandHooks is the entrypoint into the hooks execution flow after
// a main CLI command was executed. It calls the hook subcommand for all
@@ -55,11 +54,8 @@ func runHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subComma
}
func invokeAndCollectHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []string {
// check if the context was cancelled before invoking hooks
select {
case <-ctx.Done():
if ctx.Err() != nil {
return nil
default:
}
pluginsCfg := cfg.Plugins
@@ -69,47 +65,65 @@ 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, cmdErrorMessage)
if !ok {
continue
tryInvokeHook := func(pluginName string, pluginCfg map[string]string) (messages []string, ok bool, err error) {
match, matched := pluginMatch(pluginCfg, subCmdStr, cmdErrorMessage)
if !matched {
return nil, false, nil
}
p, err := getPlugin(pluginName, pluginDirs, rootCmd)
if err != nil {
continue
return nil, false, err
}
hookReturn, err := p.RunHook(ctx, HookPluginData{
resp, err := p.RunHook(ctx, hooks.Request{
RootCmd: match,
Flags: flags,
CommandError: cmdErrorMessage,
})
if err != nil {
// skip misbehaving plugins, but don't halt execution
continue
return nil, false, err
}
var hookMessageData hooks.HookMessage
err = json.Unmarshal(hookReturn, &hookMessageData)
if err != nil {
continue
var message hooks.Response
if err := json.Unmarshal(resp, &message); err != nil {
return nil, false, fmt.Errorf("failed to unmarshal hook response (%q): %w", string(resp), err)
}
// currently the only hook type
if hookMessageData.Type != hooks.NextSteps {
continue
if message.Type != hooks.NextSteps {
return nil, false, errors.New("unexpected hook response type: " + strconv.Itoa(int(message.Type)))
}
processedHook, err := hooks.ParseTemplate(hookMessageData.Template, subCmd)
messages, err = hooks.ParseTemplate(message.Template, subCmd)
if err != nil {
return nil, false, err
}
return messages, true, nil
}
for pluginName, pluginCfg := range pluginsCfg {
messages, ok, err := tryInvokeHook(pluginName, pluginCfg)
if err != nil {
// skip misbehaving plugins, but don't halt execution
logrus.WithFields(logrus.Fields{
"error": err,
"plugin": pluginName,
}).Debug("Plugin hook invocation failed")
continue
}
if !ok {
continue
}
var appended bool
nextSteps, appended = appendNextSteps(nextSteps, processedHook)
nextSteps, appended = appendNextSteps(nextSteps, messages)
if !appended {
logrus.Debugf("Plugin %s responded with an empty hook message %q. Ignoring.", pluginName, string(hookReturn))
logrus.WithFields(logrus.Fields{
"plugin": pluginName,
}).Debug("Plugin responded with an empty hook message; ignoring")
}
}
return nextSteps
+2 -3
View File
@@ -177,9 +177,8 @@ func TestGetPluginDirs(t *testing.T) {
pluginDirs := getPluginDirs(cli.ConfigFile())
assert.Equal(t, strings.Join(expected, ":"), strings.Join(pluginDirs, ":"))
extras := []string{
"foo", "bar", "baz",
}
extras := make([]string, 0, 3+len(expected))
extras = append(extras, "foo", "bar", "baz")
expected = append(extras, expected...)
cli.SetConfigFile(&configfile.ConfigFile{
CLIPluginsExtraDirs: extras,
+11 -6
View File
@@ -12,6 +12,7 @@ import (
"strconv"
"strings"
"github.com/docker/cli/cli-plugins/hooks"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/spf13/cobra"
)
@@ -154,7 +155,7 @@ func validateSchemaVersion(version string) error {
// RunHook executes the plugin's hooks command
// and returns its unprocessed output.
func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte, error) {
func (p *Plugin) RunHook(ctx context.Context, hookData hooks.Request) ([]byte, error) {
hDataBytes, err := json.Marshal(hookData)
if err != nil {
return nil, wrapAsPluginError(err, "failed to marshall hook data")
@@ -163,12 +164,16 @@ func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte,
pCmd := exec.CommandContext(ctx, p.Path, p.Name, metadata.HookSubcommandName, string(hDataBytes)) // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
pCmd.Env = os.Environ()
pCmd.Env = append(pCmd.Env, metadata.ReexecEnvvar+"="+os.Args[0])
hookCmdOutput, err := pCmd.Output()
if err != nil {
return nil, wrapAsPluginError(err, "failed to execute plugin hook subcommand")
}
return hookCmdOutput, nil
out, err := pCmd.Output()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
return nil, wrapAsPluginError(err, "plugin hook subcommand exited unsuccessfully")
}
return nil, wrapAsPluginError(err, "failed to execute plugin hook subcommand: "+pCmd.String())
}
return out, nil
}
// pluginNameFormat is used as part of errors for invalid plugin-names.
+3 -3
View File
@@ -54,9 +54,9 @@ func TestPluginServer(t *testing.T) {
})
t.Run("allows reconnects", func(t *testing.T) {
var calls int32
var calls atomic.Int32
h := func(_ net.Conn) {
atomic.AddInt32(&calls, 1)
calls.Add(1)
}
srv, err := NewPluginServer(h)
@@ -70,7 +70,7 @@ func TestPluginServer(t *testing.T) {
waitForCalls := func(n int) {
poll.WaitOn(t, func(t poll.LogT) poll.Result {
if atomic.LoadInt32(&calls) == int32(n) {
if calls.Load() == int32(n) {
return poll.Success()
}
return poll.Continue("waiting for handler to be called")
+4 -3
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package command
@@ -566,11 +566,12 @@ type ServerInfo struct {
// It applies by default the standard streams, and the content trust from
// environment.
func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
defaultOps := []CLIOption{
defaultOps := make([]CLIOption, 0, 3+len(ops))
defaultOps = append(defaultOps,
WithDefaultContextStoreConfig(),
WithStandardStreams(),
WithUserAgent(UserAgent()),
}
)
ops = append(defaultOps, ops...)
cli := &DockerCli{baseCtx: context.Background()}
+55 -15
View File
@@ -5,7 +5,6 @@ import (
"strings"
"github.com/distribution/reference"
"github.com/docker/cli/cli/command/formatter"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
@@ -23,7 +22,7 @@ type APIClientProvider interface {
// ImageNames offers completion for images present within the local store
func ImageNames(dockerCLI APIClientProvider, limit int) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return Unique(func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if limit > 0 && len(args) >= limit {
return nil, cobra.ShellCompDirectiveNoFileComp
}
@@ -36,14 +35,14 @@ func ImageNames(dockerCLI APIClientProvider, limit int) cobra.CompletionFunc {
names = append(names, img.RepoTags...)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
})
}
// ImageNamesWithBase offers completion for images present within the local store,
// including both full image names with tags and base image names (repository names only)
// when multiple tags exist for the same base name
func ImageNamesWithBase(dockerCLI APIClientProvider, limit int) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return Unique(func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if limit > 0 && len(args) >= limit {
return nil, cobra.ShellCompDirectiveNoFileComp
}
@@ -69,14 +68,14 @@ func ImageNamesWithBase(dockerCLI APIClientProvider, limit int) cobra.Completion
}
}
return names, cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
}
})
}
// ContainerNames offers completion for container names and IDs
// By default, only names are returned.
// Set DOCKER_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs.
func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(container.Summary) bool) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return Unique(func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
res, err := dockerCLI.Client().ContainerList(cmd.Context(), client.ContainerListOptions{
All: all,
})
@@ -101,15 +100,21 @@ func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(conta
if showContainerIDs {
names = append(names, ctr.ID)
}
names = append(names, formatter.StripNamePrefix(ctr.Names)...)
for _, n := range ctr.Names {
// Skip legacy link names: "/linked-container/link-name"
if len(n) <= 1 || strings.IndexByte(n[1:], '/') != -1 {
continue
}
names = append(names, strings.TrimPrefix(n, "/"))
}
}
return names, cobra.ShellCompDirectiveNoFileComp
}
})
}
// VolumeNames offers completion for volumes
func VolumeNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return Unique(func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
res, err := dockerCLI.Client().VolumeList(cmd.Context(), client.VolumeListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
@@ -119,12 +124,12 @@ func VolumeNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
names = append(names, vol.Name)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
})
}
// NetworkNames offers completion for networks
func NetworkNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return Unique(func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
res, err := dockerCLI.Client().NetworkList(cmd.Context(), client.NetworkListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
@@ -134,7 +139,7 @@ func NetworkNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
names = append(names, nw.Name)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
})
}
// EnvVarNames offers completion for environment-variable names. This
@@ -151,7 +156,7 @@ func NetworkNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
// docker run --rm --env MY_VAR alpine printenv MY_VAR
// hello
func EnvVarNames() cobra.CompletionFunc {
return func(_ *cobra.Command, _ []string, _ string) (names []string, _ cobra.ShellCompDirective) {
return Unique(func(_ *cobra.Command, _ []string, _ string) (names []string, _ cobra.ShellCompDirective) {
envs := os.Environ()
names = make([]string, 0, len(envs))
for _, env := range envs {
@@ -159,12 +164,12 @@ func EnvVarNames() cobra.CompletionFunc {
names = append(names, name)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
})
}
// FromList offers completion for the given list of options.
func FromList(options ...string) cobra.CompletionFunc {
return cobra.FixedCompletions(options, cobra.ShellCompDirectiveNoFileComp)
return Unique(cobra.FixedCompletions(options, cobra.ShellCompDirectiveNoFileComp))
}
// FileNames is a convenience function to use [cobra.ShellCompDirectiveDefault],
@@ -218,3 +223,38 @@ func Platforms() cobra.CompletionFunc {
return commonPlatforms, cobra.ShellCompDirectiveNoFileComp
}
}
// Unique wraps a completion func and removes completion results that are
// already consumed (i.e., appear in "args").
//
// For example:
//
// # initial completion: args is empty, so all results are shown
// command <tab>
// one two three
//
// # "one" is already used so omitted
// command one <tab>
// two three
func Unique(fn cobra.CompletionFunc) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
all, dir := fn(cmd, args, toComplete)
if len(all) == 0 || len(args) == 0 {
return all, dir
}
alreadyCompleted := make(map[string]struct{}, len(args))
for _, a := range args {
alreadyCompleted[a] = struct{}{}
}
out := make([]string, 0, len(all))
for _, c := range all {
if _, ok := alreadyCompleted[c]; !ok {
out = append(out, c)
}
}
return out, dir
}
}
+25 -5
View File
@@ -84,7 +84,7 @@ func TestCompleteContainerNames(t *testing.T) {
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b"}},
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
},
expOut: []string{"container-c", "container-c/link-b", "container-b", "container-a"},
expOut: []string{"container-c", "container-b", "container-a"},
expOpts: client.ContainerListOptions{All: true},
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
@@ -97,7 +97,7 @@ func TestCompleteContainerNames(t *testing.T) {
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b"}},
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
},
expOut: []string{"id-c", "container-c", "container-c/link-b", "id-b", "container-b", "id-a", "container-a"},
expOut: []string{"id-c", "container-c", "id-b", "container-b", "id-a", "container-a"},
expOpts: client.ContainerListOptions{All: true},
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
@@ -107,7 +107,7 @@ func TestCompleteContainerNames(t *testing.T) {
containers: []container.Summary{
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b"}},
},
expOut: []string{"container-c", "container-c/link-b"},
expOut: []string{"container-c"},
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
@@ -117,7 +117,7 @@ func TestCompleteContainerNames(t *testing.T) {
func(ctr container.Summary) bool { return ctr.State == container.StateCreated },
},
containers: []container.Summary{
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b"}},
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c"}},
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b"}},
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
},
@@ -133,7 +133,7 @@ func TestCompleteContainerNames(t *testing.T) {
func(ctr container.Summary) bool { return ctr.State == container.StateCreated },
},
containers: []container.Summary{
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b"}},
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c"}},
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b"}},
{ID: "id-a", State: container.StateCreated, Names: []string{"/container-a"}},
},
@@ -351,3 +351,23 @@ func TestCompleteVolumeNames(t *testing.T) {
})
}
}
func TestUnique(t *testing.T) {
base := []string{"alpha", "beta", "gamma"}
comp := Unique(func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
return base, cobra.ShellCompDirectiveNoFileComp
})
values, directives := comp(&cobra.Command{}, []string{"beta"}, "")
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp))
assert.Check(t, is.DeepEqual(values, []string{"alpha", "gamma"}))
assert.Check(t, is.DeepEqual(base, []string{"alpha", "beta", "gamma"}))
values, directives = comp(&cobra.Command{}, []string{"gamma"}, "")
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp))
assert.Check(t, is.DeepEqual(values, []string{"alpha", "beta"}))
assert.Check(t, is.DeepEqual(base, []string{"alpha", "beta", "gamma"}))
}
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package config
+8
View File
@@ -36,6 +36,7 @@ type fakeClient struct {
infoFunc func() (client.SystemInfoResult, error)
containerStatPathFunc func(containerID, path string) (client.ContainerStatPathResult, error)
containerCopyFromFunc func(containerID, srcPath string) (client.CopyFromContainerResult, error)
containerCopyToFunc func(containerID string, options client.CopyToContainerOptions) (client.CopyToContainerResult, error)
logFunc func(string, client.ContainerLogsOptions) (client.ContainerLogsResult, error)
waitFunc func(string) client.ContainerWaitResult
containerListFunc func(client.ContainerListOptions) (client.ContainerListResult, error)
@@ -128,6 +129,13 @@ func (f *fakeClient) CopyFromContainer(_ context.Context, containerID string, op
return client.CopyFromContainerResult{}, nil
}
func (f *fakeClient) CopyToContainer(_ context.Context, containerID string, options client.CopyToContainerOptions) (client.CopyToContainerResult, error) {
if f.containerCopyToFunc != nil {
return f.containerCopyToFunc(containerID, options)
}
return client.CopyToContainerResult{}, nil
}
func (f *fakeClient) ContainerLogs(_ context.Context, containerID string, options client.ContainerLogsOptions) (client.ContainerLogsResult, error) {
if f.logFunc != nil {
return f.logFunc(containerID, options)
+30 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package container
@@ -182,6 +182,35 @@ func completeLink(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
}
}
// completeLinks implements shell completion for the `--link` option of `rm --link`.
//
// It contacts the API to get names of legacy links on containers.
// In case of an error, an empty list is returned.
func completeLinks(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
res, err := dockerCLI.Client().ContainerList(cmd.Context(), client.ContainerListOptions{
All: true,
})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, ctr := range res.Items {
if len(ctr.Names) <= 1 {
// Container has no links names.
continue
}
for _, n := range ctr.Names {
// Skip legacy link names: "/linked-container/link-name"
if len(n) > 1 && strings.IndexByte(n[1:], '/') != -1 {
names = append(names, strings.TrimPrefix(n, "/"))
}
}
}
return names, cobra.ShellCompDirectiveNoFileComp
}
}
// completeLogDriver implements shell completion for the `--log-driver` option of `run` and `create`.
// The log drivers are collected from a call to the Info endpoint with a fallback to a hard-coded list
// of the build-in log drivers.
+41
View File
@@ -135,3 +135,44 @@ func TestCompleteSignals(t *testing.T) {
assert.Check(t, len(values) > 1)
assert.Check(t, is.Len(values, len(signal.SignalMap)))
}
func TestCompleteLinks(t *testing.T) {
tests := []struct {
doc string
showAll, showIDs bool
filters []func(container.Summary) bool
containers []container.Summary
expOut []string
expDirective cobra.ShellCompDirective
}{
{
doc: "no results",
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
{
doc: "all containers",
showAll: true,
containers: []container.Summary{
{ID: "id-c", State: container.StateRunning, Names: []string{"/container-c", "/container-c/link-b", "/container-c/link-c"}},
{ID: "id-b", State: container.StateCreated, Names: []string{"/container-b", "/container-b/link-a"}},
{ID: "id-a", State: container.StateExited, Names: []string{"/container-a"}},
},
expOut: []string{"container-c/link-b", "container-c/link-c", "container-b/link-a"},
expDirective: cobra.ShellCompDirectiveNoFileComp,
},
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
comp := completeLinks(test.NewFakeCli(&fakeClient{
containerListFunc: func(client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{Items: tc.containers}, nil
},
}))
containers, directives := comp(&cobra.Command{}, nil, "")
assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective))
assert.Check(t, is.DeepEqual(containers, tc.expOut))
})
}
}
+59 -2
View File
@@ -168,6 +168,50 @@ func progressHumanSize(n int64) string {
return units.HumanSizeWithPrecision(float64(n), 3)
}
// localContentSize returns the total size of regular file content at path.
// For a regular file it returns the file size. For a directory it walks
// the tree and sums sizes of all regular files.
func localContentSize(path string) (int64, error) {
fi, err := os.Lstat(path)
if err != nil {
return -1, err
}
if !fi.IsDir() {
if fi.Mode().IsRegular() {
return fi.Size(), nil
}
return 0, nil
}
var total int64
err = filepath.WalkDir(path, func(_ string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.Type().IsRegular() {
info, err := d.Info()
if err != nil {
return err
}
total += info.Size()
}
return nil
})
return total, err
}
// copySummary formats the "Successfully copied ..." message.
// When contentSize differs from transferredSize, both values are shown.
func copySummary(contentSize, transferredSize int64, dest string) string {
if contentSize != transferredSize {
return fmt.Sprintf("Successfully copied %s (transferred %s) to %s\n",
progressHumanSize(contentSize), progressHumanSize(transferredSize), dest,
)
}
return fmt.Sprintf("Successfully copied %s to %s\n",
progressHumanSize(contentSize), dest,
)
}
func runCopy(ctx context.Context, dockerCli command.Cli, opts copyOptions) error {
srcContainer, srcPath := splitCpArg(opts.source)
destContainer, destPath := splitCpArg(opts.destination)
@@ -295,7 +339,11 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
cancel()
<-done
restore()
_, _ = fmt.Fprintln(dockerCLI.Err(), "Successfully copied", progressHumanSize(copiedSize), "to", dstPath)
reportedSize := copiedSize
if !cpRes.Stat.Mode.IsDir() {
reportedSize = cpRes.Stat.Size
}
_, _ = fmt.Fprint(dockerCLI.Err(), copySummary(reportedSize, copiedSize, dstPath))
return res
}
@@ -354,11 +402,14 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
content io.ReadCloser
resolvedDstPath string
copiedSize int64
contentSize int64
sizeErr error
)
if srcPath == "-" {
content = os.Stdin
resolvedDstPath = dstInfo.Path
sizeErr = errors.New("content size not available for stdin")
if !dstInfo.IsDir {
return fmt.Errorf(`destination "%s:%s" must be a directory`, copyConfig.container, dstPath)
}
@@ -369,6 +420,8 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
return err
}
contentSize, sizeErr = localContentSize(srcInfo.Path)
srcArchive, err := archive.TarResource(srcInfo)
if err != nil {
return err
@@ -421,7 +474,11 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
cancel()
<-done
restore()
_, _ = fmt.Fprintln(dockerCLI.Err(), "Successfully copied", progressHumanSize(copiedSize), "to", copyConfig.container+":"+dstInfo.Path)
reportedSize := copiedSize
if sizeErr == nil {
reportedSize = contentSize
}
_, _ = fmt.Fprint(dockerCLI.Err(), copySummary(reportedSize, copiedSize, copyConfig.container+":"+dstInfo.Path))
return err
}
+235
View File
@@ -11,6 +11,7 @@ import (
"github.com/docker/cli/internal/test"
"github.com/moby/go-archive"
"github.com/moby/go-archive/compression"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@@ -211,3 +212,237 @@ func TestRunCopyFromContainerToFilesystemIrregularDestination(t *testing.T) {
expected := `"/dev/random" must be a directory or a regular file`
assert.ErrorContains(t, err, expected)
}
func TestCopySummary(t *testing.T) {
tests := []struct {
name string
contentSize int64
transferredSize int64
dest string
wantContains string
wantNoContain string
}{
{
name: "different sizes shows both",
contentSize: 5,
transferredSize: 2048,
dest: "/dst",
wantContains: "(transferred",
},
{
name: "equal sizes shows single value",
contentSize: 100,
transferredSize: 100,
dest: "/dst",
wantNoContain: "(transferred",
},
{
name: "both zero",
contentSize: 0,
transferredSize: 0,
dest: "ctr:/dst",
wantContains: "Successfully copied 0B to ctr:/dst",
wantNoContain: "(transferred",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := copySummary(tc.contentSize, tc.transferredSize, tc.dest)
if tc.wantContains != "" {
assert.Check(t, is.Contains(got, tc.wantContains))
}
if tc.wantNoContain != "" {
assert.Check(t, !strings.Contains(got, tc.wantNoContain), "unexpected substring %q in %q", tc.wantNoContain, got)
}
})
}
}
func TestCopyFromContainerReportsFileSize(t *testing.T) {
// The file content is "hello" (5 bytes), but the TAR archive wrapping
// it is much larger due to headers and padding. The success message
// should report the actual file size (5B), not the TAR stream size.
srcDir := fs.NewDir(t, "cp-test-from",
fs.WithFile("file1", "hello"))
destDir := fs.NewDir(t, "cp-test-from-dest")
const fileSize int64 = 5
fakeCli := test.NewFakeCli(&fakeClient{
containerCopyFromFunc: func(ctr, srcPath string) (client.CopyFromContainerResult, error) {
readCloser, err := archive.Tar(srcDir.Path(), compression.None)
return client.CopyFromContainerResult{
Content: readCloser,
Stat: container.PathStat{
Name: "file1",
Size: fileSize,
},
}, err
},
})
err := runCopy(context.TODO(), fakeCli, copyOptions{
source: "container:/file1",
destination: destDir.Path(),
})
assert.NilError(t, err)
errOut := fakeCli.ErrBuffer().String()
assert.Check(t, is.Contains(errOut, "Successfully copied 5B"))
assert.Check(t, is.Contains(errOut, "(transferred"))
}
func TestCopyToContainerReportsFileSize(t *testing.T) {
// Create a temp file with known content ("hello" = 5 bytes).
// The TAR archive sent to the container is larger, but the success
// message should report the actual content size.
srcFile := fs.NewFile(t, "cp-test-to", fs.WithContent("hello"))
fakeCli := test.NewFakeCli(&fakeClient{
containerStatPathFunc: func(containerID, path string) (client.ContainerStatPathResult, error) {
return client.ContainerStatPathResult{
Stat: container.PathStat{
Name: "tmp",
Mode: os.ModeDir | 0o755,
},
}, nil
},
containerCopyToFunc: func(containerID string, options client.CopyToContainerOptions) (client.CopyToContainerResult, error) {
_, _ = io.Copy(io.Discard, options.Content)
return client.CopyToContainerResult{}, nil
},
})
err := runCopy(context.TODO(), fakeCli, copyOptions{
source: srcFile.Path(),
destination: "container:/tmp",
})
assert.NilError(t, err)
errOut := fakeCli.ErrBuffer().String()
assert.Check(t, is.Contains(errOut, "Successfully copied 5B"))
assert.Check(t, is.Contains(errOut, "(transferred"))
}
func TestCopyToContainerReportsEmptyFileSize(t *testing.T) {
srcFile := fs.NewFile(t, "cp-test-empty", fs.WithContent(""))
fakeCli := test.NewFakeCli(&fakeClient{
containerStatPathFunc: func(containerID, path string) (client.ContainerStatPathResult, error) {
return client.ContainerStatPathResult{
Stat: container.PathStat{
Name: "tmp",
Mode: os.ModeDir | 0o755,
},
}, nil
},
containerCopyToFunc: func(containerID string, options client.CopyToContainerOptions) (client.CopyToContainerResult, error) {
_, _ = io.Copy(io.Discard, options.Content)
return client.CopyToContainerResult{}, nil
},
})
err := runCopy(context.TODO(), fakeCli, copyOptions{
source: srcFile.Path(),
destination: "container:/tmp",
})
assert.NilError(t, err)
errOut := fakeCli.ErrBuffer().String()
assert.Check(t, is.Contains(errOut, "Successfully copied 0B"))
assert.Check(t, is.Contains(errOut, "(transferred"))
}
func TestCopyToContainerReportsDirectorySize(t *testing.T) {
// Create a temp directory with files "aaa" (3 bytes) + "bbb" (3 bytes) = 6 bytes.
// The TAR archive is much larger, but the success message should report 6B.
srcDir := fs.NewDir(t, "cp-test-dir",
fs.WithFile("aaa", "aaa"),
fs.WithFile("bbb", "bbb"),
)
fakeCli := test.NewFakeCli(&fakeClient{
containerStatPathFunc: func(containerID, path string) (client.ContainerStatPathResult, error) {
return client.ContainerStatPathResult{
Stat: container.PathStat{
Name: "tmp",
Mode: os.ModeDir | 0o755,
},
}, nil
},
containerCopyToFunc: func(containerID string, options client.CopyToContainerOptions) (client.CopyToContainerResult, error) {
_, _ = io.Copy(io.Discard, options.Content)
return client.CopyToContainerResult{}, nil
},
})
err := runCopy(context.TODO(), fakeCli, copyOptions{
source: srcDir.Path() + string(os.PathSeparator),
destination: "container:/tmp",
})
assert.NilError(t, err)
errOut := fakeCli.ErrBuffer().String()
assert.Check(t, is.Contains(errOut, "Successfully copied 6B"))
assert.Check(t, is.Contains(errOut, "(transferred"))
}
func TestCopyFromContainerReportsDirectorySize(t *testing.T) {
// When copying a directory from a container, cpRes.Stat.Mode.IsDir() is true,
// so reportedSize falls back to copiedSize (the tar stream bytes).
srcDir := fs.NewDir(t, "cp-test-fromdir",
fs.WithFile("file1", "hello"))
destDir := fs.NewDir(t, "cp-test-fromdir-dest")
fakeCli := test.NewFakeCli(&fakeClient{
containerCopyFromFunc: func(ctr, srcPath string) (client.CopyFromContainerResult, error) {
readCloser, err := archive.Tar(srcDir.Path(), compression.None)
return client.CopyFromContainerResult{
Content: readCloser,
Stat: container.PathStat{
Name: "mydir",
Mode: os.ModeDir | 0o755,
},
}, err
},
})
err := runCopy(context.TODO(), fakeCli, copyOptions{
source: "container:/mydir",
destination: destDir.Path(),
})
assert.NilError(t, err)
errOut := fakeCli.ErrBuffer().String()
assert.Check(t, is.Contains(errOut, "Successfully copied"))
// For directories from container, content size is unknown so
// reportedSize == copiedSize and "(transferred ...)" is omitted.
assert.Check(t, !strings.Contains(errOut, "(transferred"))
}
func TestCopyToContainerStdinReportsTransferredSize(t *testing.T) {
// When copying from stdin, content size is unknown.
// The message should report transferred bytes without "(transferred ...)".
r, w, _ := os.Pipe()
_, _ = w.WriteString("some data from stdin")
w.Close()
oldStdin := os.Stdin
os.Stdin = r
t.Cleanup(func() { os.Stdin = oldStdin })
fakeCli := test.NewFakeCli(&fakeClient{
containerStatPathFunc: func(containerID, path string) (client.ContainerStatPathResult, error) {
return client.ContainerStatPathResult{
Stat: container.PathStat{
Name: "tmp",
Mode: os.ModeDir | 0o755,
},
}, nil
},
containerCopyToFunc: func(containerID string, options client.CopyToContainerOptions) (client.CopyToContainerResult, error) {
_, _ = io.Copy(io.Discard, options.Content)
return client.CopyToContainerResult{}, nil
},
})
err := runCopy(context.TODO(), fakeCli, copyOptions{
source: "-",
destination: "container:/tmp",
})
assert.NilError(t, err)
errOut := fakeCli.ErrBuffer().String()
assert.Check(t, is.Contains(errOut, "Successfully copied"))
// stdin has no content size, so reportedSize == copiedSize and
// "(transferred ...)" should not appear.
assert.Check(t, !strings.Contains(errOut, "(transferred"))
}
+29 -22
View File
@@ -2,6 +2,7 @@ package container
import (
"strconv"
"strings"
"sync"
"github.com/docker/cli/cli/command/formatter"
@@ -111,31 +112,27 @@ func NewStatsFormat(source, osType string) formatter.Format {
return formatter.Format(source)
}
// NewStats returns a new Stats entity and sets in it the given name
func NewStats(container string) *Stats {
return &Stats{StatsEntry: StatsEntry{Container: container}}
// NewStats returns a new Stats entity using the given ID, ID-prefix, or
// name to resolve the container.
func NewStats(idOrName string) *Stats {
// FIXME(thaJeztah): "idOrName" is used for fuzzy-matching the container, which can result in multiple stats for the same container.
// We should resolve the canonical ID once, then use that as reference
// to prevent duplicates. Various parts in the code compare Container
// against "ID" only (not considering "name" or "ID-prefix").
return &Stats{StatsEntry: StatsEntry{Container: idOrName}}
}
// statsFormatWrite renders the context for a list of containers statistics
func statsFormatWrite(ctx formatter.Context, stats []StatsEntry, osType string, trunc bool) error {
render := func(format func(subContext formatter.SubContext) error) error {
for _, cstats := range stats {
statsCtx := &statsContext{
s: cstats,
os: osType,
trunc: trunc,
}
if err := format(statsCtx); err != nil {
return err
}
}
return nil
}
// TODO(thaJeztah): this should be taken from the (first) StatsEntry instead.
// also, assuming all stats are for the same platform (and basing the
// column headers on that) won't allow aggregated results, which could
// be mixed platform.
memUsage := memUseHeader
if osType == winOSType {
memUsage = winMemUseHeader
}
statsCtx := statsContext{}
statsCtx := statsContext{os: osType}
statsCtx.Header = formatter.SubHeaderContext{
"Container": containerHeader,
"Name": formatter.NameHeader,
@@ -147,8 +144,18 @@ func statsFormatWrite(ctx formatter.Context, stats []StatsEntry, osType string,
"BlockIO": blockIOHeader,
"PIDs": pidsHeader,
}
statsCtx.os = osType
return ctx.Write(&statsCtx, render)
return ctx.Write(&statsCtx, func(format func(subContext formatter.SubContext) error) error {
for _, cstats := range stats {
if err := format(&statsContext{
s: cstats,
os: osType,
trunc: trunc,
}); err != nil {
return err
}
}
return nil
})
}
type statsContext struct {
@@ -167,9 +174,9 @@ func (c *statsContext) Container() string {
}
func (c *statsContext) Name() string {
// TODO(thaJeztah): make this explicitly trim the "/" prefix, not just any char.
if len(c.s.Name) > 1 {
return c.s.Name[1:]
// Trim the "/" prefix (if present).
if name := strings.TrimPrefix(c.s.Name, "/"); name != "" {
return name
}
return noValue
}
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package container
+2 -2
View File
@@ -1,5 +1,5 @@
// FIXME(vvoland): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.25
package container
+12 -3
View File
@@ -27,6 +27,11 @@ type rmOptions struct {
func newRmCommand(dockerCLI command.Cli) *cobra.Command {
var opts rmOptions
completeLinkNames := completeLinks(dockerCLI)
completeNames := completion.ContainerNames(dockerCLI, true, func(ctr container.Summary) bool {
return opts.force || ctr.State == container.StateExited || ctr.State == container.StateCreated
})
cmd := &cobra.Command{
Use: "rm [OPTIONS] CONTAINER [CONTAINER...]",
Short: "Remove one or more containers",
@@ -38,9 +43,13 @@ func newRmCommand(dockerCLI command.Cli) *cobra.Command {
Annotations: map[string]string{
"aliases": "docker container rm, docker container remove, docker rm",
},
ValidArgsFunction: completion.ContainerNames(dockerCLI, true, func(ctr container.Summary) bool {
return opts.force || ctr.State == container.StateExited || ctr.State == container.StateCreated
}),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if opts.rmLink {
// "--link" (remove link) is set; provide link names instead of container (primary) names.
return completeLinkNames(cmd, args, toComplete)
}
return completeNames(cmd, args, toComplete)
},
DisableFlagsInUseLine: true,
}
+67 -65
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package container
@@ -7,9 +7,7 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"strings"
"sync"
"time"
@@ -144,39 +142,32 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
}
eh := newEventHandler()
addEvents := []events.Action{events.ActionStart}
if options.All {
eh.setHandler(events.ActionCreate, func(e events.Message) {
if s := NewStats(e.Actor.ID); cStats.add(s) {
waitFirst.Add(1)
log.G(ctx).WithFields(log.Fields{
"event": e.Action,
"container": e.Actor.ID,
}).Debug("collecting stats for container")
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
}
})
addEvents = append(addEvents, events.ActionCreate)
}
eh.setHandler(events.ActionStart, func(e events.Message) {
eh.setHandler(addEvents, func(ctx context.Context, e events.Message) {
if s := NewStats(e.Actor.ID); cStats.add(s) {
waitFirst.Add(1)
log.G(ctx).WithFields(log.Fields{
"event": e.Action,
"container": e.Actor.ID,
}).Debug("collecting stats for container")
log.G(ctx).Debug("collecting stats for container")
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
}
})
// Remove containers when they are removed ("destroyed"); containers
// do not emit [events.ActionRemove], only [events.ActionDestroy].
//
// When running with "--all" we don't remove containers when they die,
// because they may come back, but without "--all" we remove them
// on the first possible occasion (either "die" or "destroy").
rmEvents := []events.Action{events.ActionDestroy}
if !options.All {
eh.setHandler(events.ActionDie, func(e events.Message) {
log.G(ctx).WithFields(log.Fields{
"event": e.Action,
"container": e.Actor.ID,
}).Debug("stop collecting stats for container")
cStats.remove(e.Actor.ID)
})
rmEvents = append(rmEvents, events.ActionDie)
}
eh.setHandler(rmEvents, func(ctx context.Context, e events.Message) {
log.G(ctx).Debug("stop collecting stats for container")
cStats.remove(e.Actor.ID)
})
// monitorContainerEvents watches for container creation and removal (only
// used when calling `docker stats` without arguments).
@@ -216,7 +207,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
}
eventChan := make(chan events.Message)
go eh.watch(eventChan)
go eh.watch(ctx, eventChan)
stopped := make(chan struct{})
go monitorContainerEvents(started, eventChan, stopped)
defer close(stopped)
@@ -294,30 +285,32 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
}
}
// Buffer to store formatted stats text.
// Once formatted, it will be printed in one write to avoid screen flickering.
var statsTextBuffer bytes.Buffer
// renderBuf holds the formatted stats output produced by statsFormatWrite.
// It does not include any terminal control sequences.
var renderBuf bytes.Buffer
// frameBuf holds the final terminal frame, including cursor movement and
// line-clearing escape sequences, written in a single pass to avoid flicker.
var frameBuf bytes.Buffer
statsCtx := formatter.Context{
Output: &statsTextBuffer,
Output: &renderBuf,
Format: NewStatsFormat(format, daemonOSType),
}
if options.NoStream {
cStats.mu.RLock()
ccStats := make([]StatsEntry, 0, len(cStats.cs))
for _, c := range cStats.cs {
ccStats = append(ccStats, c.GetStatistics())
}
cStats.mu.RUnlock()
if len(ccStats) == 0 {
statsList := cStats.snapshot()
if len(statsList) == 0 {
return nil
}
ccStats := make([]StatsEntry, 0, len(statsList))
for _, c := range statsList {
ccStats = append(ccStats, c.GetStatistics())
}
if err := statsFormatWrite(statsCtx, ccStats, daemonOSType, !options.NoTrunc); err != nil {
return err
}
_, _ = fmt.Fprint(dockerCLI.Out(), statsTextBuffer.String())
_, _ = dockerCLI.Out().Write(renderBuf.Bytes())
return nil
}
@@ -326,34 +319,38 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
for {
select {
case <-ticker.C:
cStats.mu.RLock()
ccStats := make([]StatsEntry, 0, len(cStats.cs))
for _, c := range cStats.cs {
renderBuf.Reset()
frameBuf.Reset()
statsList := cStats.snapshot()
if len(statsList) == 0 && !showAll {
// Clear screen
_, _ = io.WriteString(dockerCLI.Out(), "\033[H\033[J")
return nil
}
ccStats := make([]StatsEntry, 0, len(statsList))
for _, c := range statsList {
ccStats = append(ccStats, c.GetStatistics())
}
cStats.mu.RUnlock()
// Start by moving the cursor to the top-left
_, _ = fmt.Fprint(&statsTextBuffer, "\033[H")
if err := statsFormatWrite(statsCtx, ccStats, daemonOSType, !options.NoTrunc); err != nil {
return err
}
for line := range strings.SplitSeq(statsTextBuffer.String(), "\n") {
// Start by moving the cursor to the top-left
_, _ = io.WriteString(&frameBuf, "\033[H")
// TODO(thaJeztah): consider wrapping the writer to inject ANSI (line-clearing) during formatting.
// instead of post-processing the results.
for line := range bytes.SplitSeq(renderBuf.Bytes(), []byte{'\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")
_, _ = frameBuf.Write(line)
_, _ = io.WriteString(&frameBuf, "\033[K")
_ = frameBuf.WriteByte('\n')
}
// We might have fewer containers than before, so let's clear the remaining text
_, _ = fmt.Fprint(&statsTextBuffer, "\033[J")
_, _ = fmt.Fprint(dockerCLI.Out(), statsTextBuffer.String())
statsTextBuffer.Reset()
if len(ccStats) == 0 && !showAll {
return nil
}
_, _ = io.WriteString(&frameBuf, "\033[J")
_, _ = dockerCLI.Out().Write(frameBuf.Bytes())
case err, ok := <-closeChan:
if !ok || err == nil || errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
// Suppress "unexpected EOF" errors in the CLI so that
@@ -369,33 +366,38 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
// newEventHandler initializes and returns an eventHandler
func newEventHandler() *eventHandler {
return &eventHandler{handlers: make(map[events.Action]func(events.Message))}
return &eventHandler{handlers: make(map[events.Action]func(context.Context, events.Message))}
}
// eventHandler allows for registering specific events to setHandler.
type eventHandler struct {
handlers map[events.Action]func(events.Message)
handlers map[events.Action]func(context.Context, events.Message)
}
func (eh *eventHandler) setHandler(action events.Action, handler func(events.Message)) {
eh.handlers[action] = handler
func (eh *eventHandler) setHandler(actions []events.Action, handler func(context.Context, events.Message)) {
for _, action := range actions {
eh.handlers[action] = handler
}
}
// watch ranges over the passed in event chan and processes the events based on the
// handlers created for a given action.
// To stop watching, close the event chan.
func (eh *eventHandler) watch(c <-chan events.Message) {
func (eh *eventHandler) watch(ctx context.Context, c <-chan events.Message) {
for e := range c {
h, exists := eh.handlers[e.Action]
if !exists {
continue
}
if e.Actor.ID == "" {
log.G(context.TODO()).WithField("event", e).Errorf("event handler: received %s event with empty ID", e.Action)
log.G(ctx).WithField("event", e).Errorf("event handler: received %s event with empty ID", e.Action)
continue
}
logger := log.G(ctx).WithFields(log.Fields{
"event": e.Action,
"container": e.Actor.ID,
})
log.G(context.TODO()).WithField("event", e).Debugf("event handler: received %s event for: %s", e.Action, e.Actor.ID)
go h(e)
go h(log.WithLogger(ctx, logger), e)
}
}
+16
View File
@@ -49,6 +49,22 @@ func (s *stats) isKnownContainer(cid string) (int, bool) {
return -1, false
}
// snapshot returns a point-in-time copy of the tracked container list
// (the slice of *Stats pointers). The returned slice is safe for use
// without holding the stats lock, but the underlying Stats values may
// continue to change concurrently.
func (s *stats) snapshot() []*Stats {
s.mu.RLock()
defer s.mu.RUnlock()
if len(s.cs) == 0 {
return nil
}
// https://github.com/golang/go/issues/53643
cp := make([]*Stats, len(s.cs))
copy(cp, s.cs)
return cp
}
func collect(ctx context.Context, s *Stats, cli client.ContainerAPIClient, streamStats bool, waitFirst *sync.WaitGroup) { //nolint:gocyclo
var getFirst bool
+3
View File
@@ -1,3 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.25
package container
import (
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package command
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package context
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package context
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package context
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package context
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package context
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package command
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package command
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package command
+22 -11
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package formatter
@@ -141,25 +141,36 @@ func (c *ContainerContext) ID() string {
// Names returns a comma-separated string of the container's names, with their
// slash (/) prefix stripped. Additional names for the container (related to the
// legacy `--link` feature) are omitted.
// legacy `--link` feature) are omitted when formatting "truncated".
func (c *ContainerContext) Names() string {
names := StripNamePrefix(c.c.Names)
if c.trunc {
for _, name := range names {
if len(strings.Split(name, "/")) == 1 {
names = []string{name}
break
var b strings.Builder
for i, n := range c.c.Names {
name := strings.TrimPrefix(n, "/")
if c.trunc {
// When printing truncated, we only print a single name.
//
// Pick the first name that's not a legacy link (does not have
// slashes inside the name itself (e.g., "/other-container/link")).
// Normally this would be the first name found.
if strings.IndexByte(name, '/') == -1 {
return name
}
continue
}
if i > 0 {
b.WriteByte(',')
}
b.WriteString(name)
}
return strings.Join(names, ",")
return b.String()
}
// StripNamePrefix removes prefix from string, typically container names as returned by `ContainersList` API
// StripNamePrefix removes any "/" prefix from container names returned
// by the "ContainersList" API.
func StripNamePrefix(ss []string) []string {
sss := make([]string, len(ss))
for i, s := range ss {
sss[i] = s[1:]
sss[i] = strings.TrimPrefix(s, "/")
}
return sss
}
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package formatter
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package formatter
-3
View File
@@ -46,7 +46,6 @@ func (ctx *DiskUsageContext) startSubsection(format Format) (*template.Template,
ctx.buffer = &bytes.Buffer{}
ctx.header = ""
ctx.Format = format
ctx.preFormat()
return ctx.parseFormat()
}
@@ -88,7 +87,6 @@ func (ctx *DiskUsageContext) Write() (err error) {
return ctx.verboseWrite()
}
ctx.buffer = &bytes.Buffer{}
ctx.preFormat()
tmpl, err := ctx.parseFormat()
if err != nil {
@@ -213,7 +211,6 @@ func (ctx *DiskUsageContext) verboseWrite() error {
return ctx.verboseWriteTable(duc)
}
ctx.preFormat()
tmpl, err := ctx.parseFormat()
if err != nil {
return err
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package formatter
+43 -35
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package formatter
@@ -33,7 +33,7 @@ func (f Format) IsTable() bool {
return strings.HasPrefix(string(f), TableFormatKey)
}
// IsJSON returns true if the format is the json format
// IsJSON returns true if the format is the JSON format
func (f Format) IsJSON() bool {
return string(f) == JSONFormatKey
}
@@ -43,6 +43,31 @@ func (f Format) Contains(sub string) bool {
return strings.Contains(string(f), sub)
}
// templateString pre-processes the format and returns it as a string
// for templating.
func (f Format) templateString() string {
out := string(f)
switch out {
case TableFormatKey:
// A bare "--format table" should already be handled before we
// hit this; a literal "table" here means a custom "table" format
// without template.
return ""
case JSONFormatKey:
// "--format json" only; not JSON formats ("--format '{{json .Field}}'").
return JSONFormat
}
// "--format 'table {{.Field}}\t{{.Field}}'" -> "{{.Field}}\t{{.Field}}"
if after, isTable := strings.CutPrefix(out, TableFormatKey); isTable {
out = after
}
out = strings.Trim(out, " ") // trim spaces, but preserve other whitespace.
out = strings.NewReplacer(`\t`, "\t", `\n`, "\n").Replace(out)
return out
}
// Context contains information required by the formatter to print the output as desired.
type Context struct {
// Output is the output stream to which the formatted string is written.
@@ -53,28 +78,12 @@ type Context struct {
Trunc bool
// internal element
finalFormat string
header any
buffer *bytes.Buffer
}
func (c *Context) preFormat() {
c.finalFormat = string(c.Format)
// TODO: handle this in the Format type
switch {
case c.Format.IsTable():
c.finalFormat = c.finalFormat[len(TableFormatKey):]
case c.Format.IsJSON():
c.finalFormat = JSONFormat
}
c.finalFormat = strings.Trim(c.finalFormat, " ")
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
c.finalFormat = r.Replace(c.finalFormat)
header any
buffer *bytes.Buffer
}
func (c *Context) parseFormat() (*template.Template, error) {
tmpl, err := templates.Parse(c.finalFormat)
tmpl, err := templates.Parse(c.Format.templateString())
if err != nil {
return nil, fmt.Errorf("template parsing error: %w", err)
}
@@ -82,20 +91,21 @@ func (c *Context) parseFormat() (*template.Template, error) {
}
func (c *Context) postFormat(tmpl *template.Template, subContext SubContext) {
if c.Output == nil {
c.Output = io.Discard
out := c.Output
if out == nil {
out = io.Discard
}
if c.Format.IsTable() {
t := tabwriter.NewWriter(c.Output, 10, 1, 3, ' ', 0)
buffer := bytes.NewBufferString("")
tmpl.Funcs(templates.HeaderFunctions).Execute(buffer, subContext.FullHeader())
buffer.WriteTo(t)
t.Write([]byte("\n"))
c.buffer.WriteTo(t)
t.Flush()
} else {
c.buffer.WriteTo(c.Output)
if !c.Format.IsTable() {
_, _ = c.buffer.WriteTo(out)
return
}
// Write column-headers and rows to the tab-writer buffer, then flush the output.
tw := tabwriter.NewWriter(out, 10, 1, 3, ' ', 0)
_ = tmpl.Funcs(templates.HeaderFunctions).Execute(tw, subContext.FullHeader())
_, _ = tw.Write([]byte{'\n'})
_, _ = c.buffer.WriteTo(tw)
_ = tw.Flush()
}
func (c *Context) contextFormat(tmpl *template.Template, subContext SubContext) error {
@@ -115,8 +125,6 @@ type SubFormat func(func(SubContext) error) error
// Write the template to the buffer using this Context
func (c *Context) Write(sub SubContext, f SubFormat) error {
c.buffer = &bytes.Buffer{}
c.preFormat()
tmpl, err := c.parseFormat()
if err != nil {
return err
+66 -11
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package formatter
@@ -8,20 +8,75 @@ import (
"testing"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestFormat(t *testing.T) {
f := Format("json")
assert.Assert(t, f.IsJSON())
assert.Assert(t, !f.IsTable())
tests := []struct {
doc string
f Format
isJSON bool
isTable bool
template string
}{
{
doc: "json format",
f: "json",
isJSON: true,
isTable: false,
template: JSONFormat,
},
{
doc: "empty table format (no template)",
f: "table",
isJSON: false,
isTable: true,
template: "",
},
{
doc: "table with escaped tabs",
f: "table {{.Field}}\\t{{.Field2}}",
isJSON: false,
isTable: true,
template: "{{.Field}}\t{{.Field2}}",
},
{
doc: "table with raw string",
f: `table {{.Field}}\t{{.Field2}}`,
isJSON: false,
isTable: true,
template: "{{.Field}}\t{{.Field2}}",
},
{
doc: "other format",
f: "other",
isJSON: false,
isTable: false,
template: "other",
},
{
doc: "other with spaces",
f: " other ",
isJSON: false,
isTable: false,
template: "other",
},
{
doc: "other with newline preserved",
f: " other\n ",
isJSON: false,
isTable: false,
template: "other\n",
},
}
f = Format("table")
assert.Assert(t, !f.IsJSON())
assert.Assert(t, f.IsTable())
f = Format("other")
assert.Assert(t, !f.IsJSON())
assert.Assert(t, !f.IsTable())
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
assert.Check(t, is.Equal(tc.f.IsJSON(), tc.isJSON))
assert.Check(t, is.Equal(tc.f.IsTable(), tc.isTable))
assert.Check(t, is.Equal(tc.f.templateString(), tc.template))
})
}
}
type fakeSubContext struct {
+2 -2
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package formatter
@@ -24,7 +24,7 @@ func MarshalJSON(x any) ([]byte, error) {
// marshalMap marshals x to map[string]any
func marshalMap(x any) (map[string]any, error) {
val := reflect.ValueOf(x)
if val.Kind() != reflect.Ptr {
if val.Kind() != reflect.Pointer {
return nil, fmt.Errorf("expected a pointer to a struct, got %v", val.Kind())
}
if val.IsNil() {
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package formatter
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package formatter
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package idresolver
+2 -16
View File
@@ -68,7 +68,7 @@ func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) {
contextDir := createTestTempDir(t)
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
chdir(t, contextDir)
t.Chdir(contextDir)
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
assert.NilError(t, err)
@@ -110,7 +110,7 @@ func TestGetContextFromLocalDirLocalFile(t *testing.T) {
func TestGetContextFromLocalDirWithCustomDockerfile(t *testing.T) {
contextDir := createTestTempDir(t)
chdir(t, contextDir)
t.Chdir(contextDir)
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
@@ -248,20 +248,6 @@ func createTestTempFile(t *testing.T, dir, filename, contents string) string {
return filePath
}
// chdir changes current working directory to dir.
// It returns a function which changes working directory back to the previous one.
// This function is meant to be executed as a deferred call.
// When an error occurs, it terminates the test.
func chdir(t *testing.T, dir string) {
t.Helper()
workingDirectory, err := os.Getwd()
assert.NilError(t, err)
assert.NilError(t, os.Chdir(dir))
t.Cleanup(func() {
assert.NilError(t, os.Chdir(workingDirectory))
})
}
func TestIsArchive(t *testing.T) {
tests := []struct {
doc string
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package image
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package image
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package image
+25 -17
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package image
@@ -206,30 +206,38 @@ var allChips = []imageChip{
chipInUse,
}
// getPossibleChips returns the list of chips used by at least one image.
// It is used to determine which columns to print (and how much width to
// reserve).
func getPossibleChips(view treeView) (chips []imageChip) {
remaining := make([]imageChip, len(allChips))
copy(remaining, allChips)
remaining := slices.Clone(allChips)
var possible []imageChip
for _, img := range view.images {
details := []imageDetails{img.Details}
for _, c := range img.Children {
details = append(details, c.Details)
check := func(d imageDetails) (done bool) {
// filter without allocating
out := remaining[:0]
for _, chip := range remaining {
if chip.check(&d) {
chips = append(chips, chip)
continue
}
out = append(out, chip)
}
remaining = out
return len(remaining) == 0
}
for _, d := range details {
for idx := len(remaining) - 1; idx >= 0; idx-- {
chip := remaining[idx]
if chip.check(&d) {
possible = append(possible, chip)
remaining = append(remaining[:idx], remaining[idx+1:]...)
}
for _, img := range view.images {
if check(img.Details) {
return chips
}
for _, c := range img.Children {
if check(c.Details) {
return chips
}
}
}
return possible
return chips
}
func printImageTree(outs command.Streams, view treeView) {
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package inspect
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package manifest
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package network
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package network
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package node
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package node
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package node
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package plugin
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package plugin
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package secret
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package service
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package service
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package service
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
// Package genericresource is a local fork of SwarmKit's [genericresource] package,
// without protobuf dependencies.
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package service
+3
View File
@@ -1,3 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.25
package progress
import (
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package service
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package stack
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package system
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package system
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package system
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
// Package pruner registers "prune" functions to be included as part of
// "docker system prune".
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package command
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package command
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package volume
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package volume
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package volume
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package convert
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package interpolation
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package interpolation
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package loader
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package loader
+1 -1
View File
@@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
//go:build go1.25
package loader

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