Compare commits

...
506 Commits
Author SHA1 Message Date
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
Paweł GronowskiandGitHub 5927d80c76 Merge pull request #6844 from vvoland/update-docker
vendor: github.com/moby/moby/api v1.54.0
2026-03-05 14:22:32 +00:00
Paweł Gronowski 206fc8c165 vendor: github.com/moby/moby/client v0.3.0
full diff: https://github.com/moby/moby/compare/client/v0.2.3-rc.1...client/v0.3.0

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

From the security mailing list:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

With this patch:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- fixes / downstream CVE-2025-61728

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

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

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

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

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

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

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

    Thanks to Thanks to Jakub Ciolek for reporting this issue.

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

- net/http: memory exhaustion in Request.ParseForm

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

    Thanks to jub0bs for reporting this issue.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- cmd/go: unexpected code execution when invoking toolchain

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Before this patch:

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

With this patch:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-17 16:45:23 +01:00
Sebastiaan van StijnandGitHub b9095d09ab Merge pull request #6710 from robmry/nri-info
Include NRI in "info" output
2025-12-16 16:33:40 +01:00
Sebastiaan van StijnandGitHub 0e6fee6c52 Merge pull request #6698 from thaJeztah/inline_parseWindowsDevice
cli/command/container: inline parseWindowsDevice, and minor cleanups
2025-12-16 16:33:03 +01:00
Rob Murray 2a903c52d4 Include NRI in info output
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-12-16 13:50:03 +00:00
Rob Murray d8351dbe65 Vendor moby/[api|client] from moby master
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-12-16 13:49:49 +00:00
Sebastiaan van StijnandGitHub 88be58884c Merge pull request #6709 from vvoland/img-list-all-doc
docs: Update --all flag description to clarify it shows dangling images
2025-12-16 12:58:57 +01:00
Paweł Gronowski f7ddc8a7d1 docs: Update --all flag description to clarify it shows dangling images
The --all flag description was misleading by only mentioning
intermediate images, when it actually also controls the visibility of
dangling (untagged) images.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Before this patch:

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

    Run 'docker run --help' for more information

With this patch:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Thanks to Jakub Ciolek for reporting this issue.

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

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

Thanks to Jakub Ciolek for reporting this issue.

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

Cheers, Go Security team

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-20 15:57:24 +00:00
Sebastiaan van StijnandGitHub 8108357bcb Merge pull request #6662 from dvdksn/doc-update-http-proxy-link
chore: update link/linktext to dockerd proxy config
2025-11-17 11:19:50 +01:00
David Karlsson 3a842587f9 chore: update link/linktext to dockerd proxy config
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2025-11-17 11:00:04 +01:00
Sebastiaan van StijnandGitHub eedd9698e9 Merge pull request #6659 from vvoland/fix-system-version
cli/command/system: Fix missing components in version output
2025-11-13 22:27:39 +01:00
Paweł Gronowski dd2c493825 cli/command/system: Fix missing components in version output
The `Components` weren't actually copied to the output struct.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-13 21:19:41 +01:00
Paweł GronowskiandGitHub 67cef775fe Merge pull request #6658 from vvoland/img-list-all-dangling
image/tree: Only show untagged images when --all flag is used
2025-11-13 20:53:26 +01:00
Paweł Gronowski 207bf52c27 image/tree: Only show untagged images when --all flag is used
In non-expanded view, untagged images should only be displayed when the
--all flag is explicitly provided by the user.

Previously, untagged images were accidentally always shown in the
non-expanded view regardless of the --all flag setting.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-13 20:23:42 +01:00
Sebastiaan van StijnandGitHub 2cfd9df568 Merge pull request #6654 from vvoland/img-list-nocolor
image/tree: Respect NO_COLOR env variable
2025-11-13 15:10:10 +01:00
Paweł Gronowski be9e6308f5 image/tree: Respect NO_COLOR env variable
Do not use the fancy colored output if NO_COLOR variable is set to 1
following the https://no-color.org/ convention.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-13 14:56:27 +01:00
Sebastiaan van StijnandGitHub 88e324150b Merge pull request #6657 from vvoland/img-list-nonexpanded-untagged
image/tree: Fix untagged images in non-expanded view
2025-11-13 13:20:46 +01:00
Sebastiaan van StijnandGitHub 2ae51e2d69 Merge pull request #6656 from vvoland/img-list-notty-width
image/tree: Don't limit name width if non tty
2025-11-13 13:19:30 +01:00
Paweł Gronowski ed281ddf52 image/list: Print legend only if limiting width
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-13 13:00:02 +01:00
Paweł Gronowski aa5d00a3a4 image/tree: Don't limit name width if non tty
Previously when no terminal was attached the width was assumed to be 80.
This is too short for most image names which truncated the names when
output was redirect (for example to `grep`).

This disabled the name truncation if the terminal width can't be
determined.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-13 12:59:46 +01:00
Paweł Gronowski b66b93130c image/tree: Fix untagged images in non-expanded view
In the expanded view there is a separate image entry per each tag.

Fix a bug which caused no entry to be added for untagged images.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-13 12:47:24 +01:00
Sebastiaan van StijnandGitHub c44e8a0727 Merge pull request #6648 from thaJeztah/cli_version_json_format
cli/command/system: define struct for formatting version
2025-11-12 18:09:53 +01:00
Sebastiaan van Stijn bff56f0493 cli/command/system: define struct for formatting version
The client.ServerVersion method in the moby/client module defines
an output struct that's separate from the API response. These output
structs are not designed to be marshaled as JSON, but the CLI depended
on them defining `json` labels, which it used to format the output
as JSON (`docker version --format=json`); as a result, the JSON output
changed in docker v29, as it would now use the naming based on the Go
struct's fields (`APIVersion` instead of `ApiVersion`).

In future, we should consider having a `--raw` (or similar) option for
the CLI to print API responses as-is, instead of using client structs
or CLI structs for this (this would also make sure the JSON output does
not inherit client-side formatting of fields).

For now, let's create a struct for formatting the output, similar to what
we do for the client-side information.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-12 14:57:13 +01:00
Sebastiaan van StijnandGitHub 3d4129b9ea Merge pull request #6644 from thaJeztah/connhelper_nowarn
cli/connhelper/commandcon: remove warn logs
2025-11-10 22:42:38 +01:00
Sebastiaan van Stijn d787e70a14 cli/connhelper/commandcon: remove warn logs
These were originally added in 6f61cf053a,
but at the time, the error wasn't returned. Now that it is, we shouldn't
log _and_ return the error.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-10 22:36:41 +01:00
Paweł GronowskiandGitHub e730f6f0f3 Merge pull request #6643 from thaJeztah/bump_modules2
vendor: github.com/moby/moby/api v1.52.0, moby/client v0.1.0
2025-11-10 22:04:44 +01:00
Paweł GronowskiandGitHub 6ac3f93755 Merge pull request #6578 from thaJeztah/bump_otel_semconv
cli/command: update to semconv v1.37.0, otel v1.38.0
2025-11-10 22:04:29 +01:00
Sebastiaan van Stijn ebc1995f9f vendor: github.com/moby/moby/api v1.52.0, moby/client v0.1.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-10 21:57:40 +01:00
Sebastiaan van StijnandGitHub 31d1a59d07 Merge pull request #6642 from vvoland/swarm-compose-work
swarm: revert compose/stack support for memory swappiness
2025-11-10 19:09:49 +01:00
Paweł Gronowski ad96811f12 swarm: Add memory swap support (no stack/compose support)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-10 17:48:56 +01:00
Paweł Gronowski 6ba06b5fb4 Revert "cli/compose: add schema 3.14 (no changes from 3.13 yet)"
This reverts commit d0c86d39ef.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-10 17:48:55 +01:00
Paweł Gronowski e0716b571f Revert "Add memory swap to swarm"
This reverts commit 71828f2792.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-10 17:48:54 +01:00
Paweł GronowskiandGitHub 179efae8b0 Merge pull request #6641 from thaJeztah/bump_modules
vendor: github.com/moby/moby/api, moby/client master
2025-11-10 17:03:43 +01:00
Sebastiaan van Stijn 4b450f113b vendor: github.com/moby/moby/api, moby/client master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-10 16:32:05 +01:00
Paweł GronowskiandGitHub ee244f2f44 Merge pull request #6636 from thaJeztah/add_missing_gobuild
cli/command/system: add missing "go:build"
2025-11-07 01:34:14 +01:00
Paweł GronowskiandGitHub 0c101c4aa7 Merge pull request #6635 from thaJeztah/bump_modules
vendor: github.com/moby/moby/api v1.52.0-rc.1, moby/client v0.1.0-rc.1
2025-11-07 01:31:10 +01:00
Sebastiaan van Stijn 1d789e4099 cli/command/system: add missing "go:build"
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 01:27:47 +01:00
Sebastiaan van Stijn b3824015d6 vendor: github.com/moby/moby/api v1.52.0-rc.1, moby/client v0.1.0-rc.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 01:26:13 +01:00
Sebastiaan van StijnandGitHub c4f240cc7d Merge pull request #6606 from dvdksn/update-libnetwork-docs-link
docs: update link to libnetwork protocol doc
2025-11-07 01:12:41 +01:00
Sebastiaan van StijnandGitHub 5b443bf269 Merge pull request #6619 from dperny/swarm-memory-swap
Add memory swap to swarm
2025-11-07 01:03:26 +01:00
Sebastiaan van StijnandGitHub eaa6114d9e Merge pull request #6634 from thaJeztah/remove_replace
vendor.mod: remove replace
2025-11-07 00:55:53 +01:00
Sebastiaan van StijnandGitHub c9e6b41293 Merge pull request #6633 from dvdksn/docs-update-dd-documentation-link
chore: update broken link to restrucured docker desktop documentation
2025-11-07 00:34:43 +01:00
Sebastiaan van Stijn d67291026e vendor.mod: remove replace
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:32:57 +01:00
Austin VazquezandSebastiaan van Stijn 41088ed7d0 vendor: go.opentelemetry.io/auto/sdk v1.2.1
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:29:26 +01:00
Austin VazquezandSebastiaan van Stijn 712f569f17 vendor: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:29:26 +01:00
Sebastiaan van Stijn 7736f5e606 vendor: align other otel packages to v1.38.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:29:24 +01:00
Jonathan A. SternbergandSebastiaan van Stijn d45551dac9 cli/command: update to semconv v1.37.0, otel v1.38.0
Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:27:34 +01:00
Drew ErnyandSebastiaan van Stijn 71828f2792 Add memory swap to swarm
Adds support for setting memory swap settings on Swarm services

* Adds flags `memory-swap` and `memory-swappiness` to `docker service
create` and `docker service update` commands.
* Adds compose fields `memswap_limit` and `mem_swappiness` for `docker
stack` commands.

Signed-off-by: Drew Erny <derny@mirantis.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:24:44 +01:00
Sebastiaan van Stijn d0c86d39ef cli/compose: add schema 3.14 (no changes from 3.13 yet)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-07 00:24:44 +01:00
Paweł GronowskiandGitHub 16d2036cde Merge pull request #6620 from austinvazquez/vendor-system-disk-usage-changes
vendor: github.com/moby/moby/api master, moby/client master
2025-11-07 00:20:18 +01:00
Austin Vazquez 5039eee77f vendor: github.com/moby/moby/api master, moby/client master
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
2025-11-06 17:02:53 -06:00
Sebastiaan van StijnandGitHub cc7275c4e5 Merge pull request #6121 from thaJeztah/trust_plugin
implement `docker trust` as plugin
2025-11-06 20:29:58 +01:00
Sebastiaan van StijnandGitHub f7d6d5bdb3 Merge pull request #6632 from vvoland/update-go
update to go1.25.4
2025-11-06 17:13:16 +01:00
Sebastiaan van Stijn cee9ea67fc lint: run in go-modules mode
Prevent the linter from recursing to other modules (cmd/docker-trust),
which don't have their dependencies vendored.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-06 15:24:49 +01:00
Sebastiaan van Stijn b2aa690b26 scripts/build/binary: remove pkcs11 build tag
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-06 15:24:49 +01:00
Sebastiaan van Stijn c1a53ae7b6 cmd/docker-trust: remove dependency on cli/internal
Create a copy of the registry package to use, so that code used only
for trust can be removed from the cli/internal package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-06 15:24:49 +01:00
Sebastiaan van Stijn 06914dd0ff make trust-plugin a separate module
skip cmd/docker-trust in tests, as it's a separate module.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-06 15:24:48 +01:00
Sebastiaan van Stijn c9bb291154 implement docker trust as plugin
move the `trust` subcommands to a plugin, so that the subcommands can
be installed separate from the `docker trust` integration in push/pull
(for situations where trust verification happens on the daemon side).

    make binary
    go build -o /usr/libexec/docker/cli-plugins/docker-trust ./cmd/docker-trust

    docker info
    Client:
     Version:    28.2.0-dev
     Context:    default
     Debug Mode: false
     Plugins:
      buildx: Docker Buildx (Docker Inc.)
        Version:  v0.24.0
        Path:     /usr/libexec/docker/cli-plugins/docker-buildx
      trust: Manage trust on Docker images (Docker Inc.)
        Version:  unknown-version
        Path:     /usr/libexec/docker/cli-plugins/docker-trust

    docker trust --help
    Usage:  docker trust [OPTIONS] COMMAND

    Extended build capabilities with BuildKit

    Options:
      -D, --debug   Enable debug logging

    Management Commands:
      key         Manage keys for signing Docker images
      signer      Manage entities who can sign Docker images

    Commands:
      inspect     Return low-level information about keys and signatures
      revoke      Remove trust for an image
      sign        Sign an image

    Run 'docker trust COMMAND --help' for more information on a command.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-06 15:24:46 +01:00
Sebastiaan van StijnandGitHub face4a61be Merge pull request #6631 from thaJeztah/fix_static_builds
Fix static build + CGO
2025-11-06 15:24:18 +01:00
David Karlsson dd70b43bc1 chore: update broken link to restrucured docker desktop documentation
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2025-11-06 14:40:16 +01:00
Paweł Gronowski f2755b02d7 update to go1.25.4
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-06 14:24:03 +01:00
Tianon GraviandSebastiaan van Stijn 880ef756b7 Fix static build + CGO
Signed-off-by: Tianon Gravi <admwiggin@gmail.com>
2025-11-06 12:39:18 +01:00
Paweł GronowskiandGitHub de00f53cb9 Merge pull request #6629 from thaJeztah/bump_x_deps
vendor: golang.org/x/* dependencies
2025-11-06 11:22:48 +01:00
Sebastiaan van Stijn 0976389e14 vendor: golang.org/x/net v0.46.0, golang.org/x/crypto v0.43.0
full diff: https://github.com/golang/net/compare/v0.39.0...v0.46.0
full diff: https://github.com/golang/crypto/compare/v0.39.0...v0.43.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 22:58:37 +01:00
Sebastiaan van Stijn c733cb0532 vendor: golang.org/x/time v0.14.0
full diff: https://github.com/golang/time/compare/v0.11.0...v0.14.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 22:47:59 +01:00
Sebastiaan van Stijn 1f77c6f1c8 vendor: golang.org/x/term v0.36.0
- term: remove duplicate flag and add comment on windows
- term: allow multi-line bracketed paste to not create single line
  with verbatim LFs (fixes "x/term: multi line bracketed paste fails"
  to issue line by line commands

full diff: https://github.com/golang/term/compare/v0.32.0...v0.36.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 22:47:50 +01:00
Sebastiaan van Stijn dcce972f47 vendor: golang.org/x/text v0.30.0
full diff: https://github.com/golang/text/compare/v0.26.0...v0.30.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 22:47:22 +01:00
Sebastiaan van Stijn b11d143cd1 vendor: golang.org/x/sync v0.17.0
full diff: https://github.com/golang/sync/compare/v0.16.0...v0.17.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 22:46:32 +01:00
Sebastiaan van Stijn 0c8ce84a62 vendor: golang.org/x/sys v0.37.0
full diff: https://github.com/golang/sys/compare/v0.33.0...v0.37.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 22:45:52 +01:00
Sebastiaan van StijnandGitHub 1c572a10de Merge pull request #6628 from thaJeztah/swarm_enums
cli/command/service: use enum-consts defined in API
2025-11-05 17:32:30 +01:00
Sebastiaan van Stijn d9f7e4b0c8 cli/command/service: use enum-consts defined in API
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 13:41:04 +01:00
Sebastiaan van StijnandGitHub 1686e45501 Merge pull request #6612 from jo-so/master
completion/zsh: Allow multiple volumes of 'volume rm'
2025-11-05 13:27:15 +01:00
Sebastiaan van StijnandGitHub 050aca80a5 Merge pull request #6627 from thaJeztah/local_parsegeneric_resource
cli/command/service: parse generic resources without protobufs
2025-11-05 12:45:12 +01:00
Paweł GronowskiandGitHub 30b02813f1 Merge pull request #6625 from thaJeztah/bump_golangci_lint
Dockerfile: update golangci-lint to v2.6.1
2025-11-05 12:40:19 +01:00
Sebastiaan van Stijn 774f1d60a1 cli/command/service: parse generic resources without protobufs
This code was using swarmkit's genericresource package as intermediate;
add a local copy of that code that skips the protobufs as intermediate.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 12:26:02 +01:00
Sebastiaan van Stijn 437ed4c1e4 Dockerfile: update golangci-lint to v2.6.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 12:24:12 +01:00
Paweł GronowskiandGitHub 9fc247e0e5 Merge pull request #6626 from thaJeztah/fix_perfsprint
fix perfsprint (concat-loop) linting
2025-11-05 12:23:06 +01:00
Paweł GronowskiandGitHub 924ed097c5 Merge pull request #6623 from thaJeztah/bump_swarmkit
vendor: github.com/moby/swarmkit/v2 v2.1.1
2025-11-05 12:22:42 +01:00
Paweł GronowskiandGitHub b52826a55e Merge pull request #6622 from thaJeztah/bump_platforms
vendor: github.com/containerd/platforms v1.0.0-rc.2
2025-11-05 12:22:30 +01:00
Paweł GronowskiandGitHub 38d271a8e4 Merge pull request #6617 from thaJeztah/bump_jose
vendor: github.com/go-jose/go-jose/v4 v4.1.3
2025-11-05 12:22:08 +01:00
Paweł GronowskiandGitHub e6d6ddf38d Merge pull request #6616 from thaJeztah/bump_runewidth
vendor: github.com/mattn/go-runewidth v0.0.19
2025-11-05 12:21:51 +01:00
Sebastiaan van Stijn c4a28d0d3c vendor: github.com/go-jose/go-jose/v4 v4.1.3
- remove Go 1.23 support
- removes dependency on golang.org/x/crypto
- reject JWS with an unprotected critical b64 header

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 11:55:19 +01:00
Sebastiaan van StijnandGitHub 5b68e72ad3 Merge pull request #6624 from thaJeztah/bump_go_minimum
update minimum go version to go1.24
2025-11-05 11:47:27 +01:00
Sebastiaan van Stijn f8d0365127 fix perfsprint (concat-loop) linting
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 09:27:52 +01:00
Sebastiaan van Stijn 7b624841c4 update minimum go version to go1.24
Various dependencies, including "golang.org/x/.."  started to update
the minimum required version,so we should follow suit for the next
release.

Note that the `//go:build` directives not necesserily have to be
updated, but it's good to keep them in sync until we have a go.mod
to control this.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 08:24:06 +01:00
Sebastiaan van Stijn 82b47c8e57 vendor: github.com/moby/swarmkit/v2 v2.1.1
full diff: https://github.com/moby/swarmkit/compare/v2.1.0...v2.1.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 08:05:32 +01:00
Sebastiaan van Stijn 3a91788135 vendor: github.com/containerd/platforms v1.0.0-rc.2
- Add WS2025 to Windows matcher and code optimizations
- use windowsMatchComparer for OSVersion match order
  Windows OS version should match based on the full OSVersion. When
  sorting a manifest, the entries should be sorted using the `Less`
  function.

full diff: https://github.com/containerd/platforms/compare/v1.0.0-rc.1...v1.0.0-rc.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 07:52:02 +01:00
Sebastiaan van StijnandGitHub 0b3c028108 Merge pull request #6618 from thaJeztah/lint_mod
lint: don't disable modules
2025-11-05 07:31:43 +01:00
Sebastiaan van Stijn 61d88c9519 lint: don't disable modules
prevent the linter from traversing the docker-trust plugin module

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-04 14:44:11 +01:00
Sebastiaan van Stijn 9bce085b13 vendor: github.com/mattn/go-runewidth v0.0.19
full diff: https://github.com/mattn/go-runewidth/compare/v0.0.17...v0.0.19

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-04 14:28:14 +01:00
Austin VazquezandGitHub 7414d73fc1 Merge pull request #6615 from thaJeztah/fix_generics
cli/command/container: fix use of generics
2025-11-04 07:26:44 -06:00
Sebastiaan van StijnandGitHub c01696ffde Merge pull request #6603 from thaJeztah/remove_trust_integration
remove support for client-side docker content trust validation
2025-11-04 14:05:39 +01:00
Sebastiaan van StijnandGitHub 8d1525a011 Merge pull request #6614 from vvoland/list-tree-header-ansi
image/tree: Fix table header having escape codes when not tty
2025-11-04 14:02:28 +01:00
Sebastiaan van Stijn e0b1ab68fe cli/command/container: fix use of generics
This was introduced in dad1d367c8, which
did not add a `//go:build` constraint to enable the use of generics (`any`).

Which causes an error when used;

 could not import github.com/docker/cli/cli/command/container (-: # github.com/docker/cli/cli/command/container
 /Users/thajeztah/go/pkg/mod/github.com/docker/cli@v29.0.0-rc.2+incompatible/cli/command/container/stats.go:148:39: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-04 13:56:23 +01:00
Paweł Gronowski e5f46499b3 image/tree: Fix table header having escape codes when not tty
When stdout is redirected to a non-tty there should be no ANSI escape
codes emitted.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-04 13:55:30 +01:00
Paweł Gronowski d5d2ed5baa image/tree: Add test for checking ansi escape output
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-04 13:55:30 +01:00
Paweł Gronowski 1a261e3f50 image/tree: Use streams interface
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-04 13:55:30 +01:00
Jörg Sommer 4893a5d5e3 completion/zsh: Allow multiple volumes of 'volume rm'
It is possible to give more than one volume for `docker volume rm`.

Signed-off-by: Jörg Sommer <joerg@jo-so.de>
2025-11-04 09:36:42 +01:00
Sebastiaan van StijnandGitHub 30d597df10 Merge pull request #6605 from Benehiko/plugins/hidden
Plugin may set itself as hidden
2025-11-03 16:58:57 +01:00
Alano Terblanche 700875b666 Plugin may set itself as hidden
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-11-03 16:36:46 +01:00
Sebastiaan van StijnandGitHub 7e32fdf1b3 Merge pull request #6611 from thaJeztah/skip_hidden_plugin_commands
cli: allManagementSubCommands: improve handling of plugin stubs
2025-11-03 15:34:01 +01:00
Sebastiaan van Stijn ad776d1e10 remove support for client-side docker content trust validation
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-03 14:21:19 +01:00
Sebastiaan van Stijn 259df25a96 cli: allManagementSubCommands: improve handling of plugin stubs
The allManagementSubCommands function is used to present plugin-commands
in the docker --help output; these commands are included in the "management
commands" section, but for plugins we don't know if they have sub-commands.

However, plugin stubs may be hidden (for placeholders that are not yet loaded),
or not be runnable, which was previously ignored.

This patch treats plugin-stubs the same as other commands, with the exception
of checking if they have subcommands (which is not yet known for plugin-stubs).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-03 13:57:01 +01:00
David Karlsson aa62a6a97a docs: update link to libnetwork protocol doc
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2025-11-03 13:32:30 +01:00
1215 changed files with 77367 additions and 108354 deletions
+9 -3
View File
@@ -1,11 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: "Contributing to Docker"
- name: "🗃️ Docker Sandboxes: report an issue"
about: "Issues with Docker Sandboxes should be filed in the Docker Desktop feedback tracker (not docker/cli)."
url: "https://github.com/docker/desktop-feedback/issues"
- name: "🖥️ Docker Desktop: report an issue"
about: "General Docker Desktop issues (installation, upgrades, UI, networking on macOS/Windows, WSL2, etc.) should be filed in the Docker Desktop feedback tracker (not docker/cli)."
url: "https://github.com/docker/desktop-feedback/issues"
- name: "🧑‍💻 Contributing to Docker"
about: "Read guidelines and tips about contributing to Docker."
url: "https://github.com/docker/cli/blob/master/CONTRIBUTING.md"
- name: "Security and Vulnerabilities"
- name: "🔒 Security and Vulnerabilities"
about: "Report any security issues or vulnerabilities responsibly to the Docker security team. Do not use the public issue tracker."
url: "https://github.com/moby/moby/security/policy"
- name: "General Support"
- name: "💬 General Support"
about: "Get the help you need to build, share, and run your Docker applications"
url: "https://www.docker.com/support/"
+2
View File
@@ -7,3 +7,5 @@ updates:
labels:
- "area/testing"
- "status/2-code-review"
cooldown:
default-days: 7
+12 -12
View File
@@ -35,7 +35,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Create matrix
id: platforms
@@ -63,10 +63,10 @@ jobs:
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Build
uses: docker/bake-action@v6
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7
with:
targets: ${{ matrix.target }}
set: |
@@ -88,7 +88,7 @@ jobs:
fi
-
name: Upload artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: ${{ env.ARTIFACT_NAME }}
path: /tmp/out/*
@@ -101,20 +101,20 @@ jobs:
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
with:
username: ${{ secrets.DOCKERHUB_CLIBIN_USERNAME }}
password: ${{ secrets.DOCKERHUB_CLIBIN_TOKEN }}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
with:
images: dockereng/cli-bin
tags: |
@@ -125,7 +125,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
-
name: Build and push image
uses: docker/bake-action@v6
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7
with:
files: |
./docker-bake.hcl
@@ -143,7 +143,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Create matrix
id: platforms
@@ -165,10 +165,10 @@ jobs:
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Build
uses: docker/bake-action@v6
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7
with:
targets: plugins-cross
set: |
+7 -6
View File
@@ -46,7 +46,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
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.3"
go-version: "1.26.1"
cache: false
-
name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
with:
languages: go
-
name: Autobuild
uses: github/codeql-action/autobuild@v4
uses: github/codeql-action/autobuild@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
with:
category: "/language:go"
+6 -6
View File
@@ -37,14 +37,14 @@ jobs:
- alpine
- debian
engine-version:
- 29-rc # latest rc
- 28 # latest
- 27 # latest - 1
- rc # latest rc
- 29 # latest
- 28 # latest - 1
- 25 # mirantis lts
steps:
-
name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
-
name: Update daemon.json
run: |
@@ -63,7 +63,7 @@ jobs:
docker info
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Run ${{ matrix.target }}
run: |
@@ -74,7 +74,7 @@ jobs:
TESTFLAGS: -coverprofile=/tmp/coverage/coverage.txt
-
name: Send to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
with:
files: ./build/coverage/coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
+12 -8
View File
@@ -30,15 +30,15 @@ jobs:
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-
name: Test
uses: docker/bake-action@v6
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7
with:
targets: test-coverage
-
name: Send to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
with:
files: ./build/coverage/coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
@@ -60,24 +60,28 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
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.3"
go-version: "1.26.1"
cache: false
-
name: Test
run: |
go test -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/')
# run in go modules mode to prevent traversing to nested modules
ln -s vendor.mod go.mod
ln -s vendor.sum go.sum
go test -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '^github.com/docker/cli/e2e/')
go tool cover -func=/tmp/coverage.txt
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
shell: bash
-
name: Send to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
with:
files: /tmp/coverage.txt
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
+27 -3
View File
@@ -38,7 +38,7 @@ jobs:
steps:
-
name: Run
uses: docker/bake-action@v6
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7
with:
targets: ${{ matrix.target }}
@@ -48,7 +48,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
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@v5
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.1"
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
+6 -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.3"
go: "1.26.1"
timeout: 5m
@@ -94,6 +94,8 @@ linters:
desc: Use github.com/google/uuid instead.
- pkg: "io/ioutil"
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
- pkg: "gopkg.in/yaml.v3"
desc: Use go.yaml.in/yaml/v3 instead.
forbidigo:
forbid:
@@ -156,11 +158,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
@@ -223,13 +220,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>
+6 -6
View File
@@ -5,14 +5,14 @@ ARG BASE_VARIANT=alpine
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
# It must be a supported tag in the docker.io/library/alpine image repository
# that's also available as alpine image variant for the Golang version used.
ARG ALPINE_VERSION=3.22
ARG ALPINE_VERSION=3.23
ARG BASE_DEBIAN_DISTRO=bookworm
ARG GO_VERSION=1.25.3
ARG GO_VERSION=1.26.1
# XX_VERSION specifies the version of the xx utility to use.
# It must be a valid tag in the docker.io/tonistiigi/xx image repository.
ARG XX_VERSION=1.7.0
ARG XX_VERSION=1.9.0
# GOVERSIONINFO_VERSION is the version of GoVersionInfo to install.
# It must be a valid tag from https://github.com/josephspurrier/goversioninfo
@@ -25,12 +25,12 @@ ARG GOTESTSUM_VERSION=v1.13.0
# BUILDX_VERSION sets the version of buildx to use for the e2e tests.
# It must be a tag in the docker.io/docker/buildx-bin image repository
# on Docker Hub.
ARG BUILDX_VERSION=0.29.1
ARG BUILDX_VERSION=0.31.1
# COMPOSE_VERSION is the version of compose to install in the dev container.
# It must be a tag in the docker.io/docker/compose-bin image repository
# on Docker Hub.
ARG COMPOSE_VERSION=v2.40.0
ARG COMPOSE_VERSION=v5.1.0
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
@@ -97,7 +97,7 @@ ENV GO111MODULE=auto
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/go/pkg/mod \
gotestsum -- -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/')
gotestsum -- -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/|/cmd/docker-trust')
FROM scratch AS test-coverage
COPY --from=test /tmp/coverage.txt /coverage.txt
+12 -3
View File
@@ -34,12 +34,12 @@ test: test-unit ## run tests
.PHONY: test-unit
test-unit: ## run unit tests, to change the output format use: GOTESTSUM_FORMAT=(dots|short|standard-quiet|short-verbose|standard-verbose) make test-unit
gotestsum -- $${TESTDIRS:-$(shell go list ./... | grep -vE '/vendor/|/e2e/')} $(TESTFLAGS)
gotestsum -- $${TESTDIRS:-$(shell go list ./... | grep -vE '/vendor/|/e2e/|/cmd/docker-trust')} $(TESTFLAGS)
.PHONY: test-coverage
test-coverage: ## run test coverage
mkdir -p $(CURDIR)/build/coverage
gotestsum -- $(shell go list ./... | grep -vE '/vendor/|/e2e/') -coverprofile=$(CURDIR)/build/coverage/coverage.txt
gotestsum -- $(shell go list ./... | grep -vE '/vendor/|/e2e/|/cmd/docker-trust') -coverprofile=$(CURDIR)/build/coverage/coverage.txt
.PHONY: lint
lint: ## run all the lint tools
@@ -52,7 +52,7 @@ shellcheck: ## run shellcheck validation
.PHONY: fmt
fmt: ## run gofumpt (if present) or gofmt
@if command -v gofumpt > /dev/null; then \
gofumpt -w -d -lang=1.23 . ; \
gofumpt -w -d -lang=1.24 . ; \
else \
go list -f {{.Dir}} ./... | xargs gofmt -w -s -d ; \
fi
@@ -69,6 +69,15 @@ dynbinary: ## build dynamically linked binary
plugins: ## build example CLI plugins
scripts/build/plugins
.PHONY: trust-plugin
trust-plugin: ## build docker-trust CLI plugins
scripts/build/trust-plugin
.PHONY: install-trust-plugin
install-trust-plugin: trust-plugin
install-trust-plugin: ## install docker-trust CLI plugins
install -D -m 0755 "$$(readlink -f build/docker-trust)" /usr/libexec/docker/cli-plugins/docker-trust
.PHONY: vendor
vendor: ## update vendor with go modules
rm -rf vendor
+1 -1
View File
@@ -1 +1 @@
29.0.0-dev
29.4.0-dev
+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)
}
})
}
}
+1 -1
View File
@@ -151,7 +151,7 @@ func TestValidateCandidate(t *testing.T) {
assert.ErrorContains(t, err, tc.err)
case tc.invalid != "":
assert.NilError(t, err)
assert.Assert(t, is.ErrorType(p.Err, reflect.TypeOf(&pluginError{})))
assert.Assert(t, is.ErrorType(p.Err, reflect.TypeFor[*pluginError]()))
assert.ErrorContains(t, p.Err, tc.invalid)
default:
assert.NilError(t, err)
+7 -3
View File
@@ -38,14 +38,14 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
rootCmd.AddCommand(&cobra.Command{
Use: p.Name,
Short: p.ShortDescription,
Hidden: p.Hidden,
Run: func(_ *cobra.Command, _ []string) {},
Annotations: annotations,
DisableFlagParsing: true,
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") {
@@ -56,10 +56,14 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Delegate completion to plugin
cargs := []string{p.Path, cobra.ShellCompRequestCmd, p.Name}
cargs := []string{p.Path, cobra.ShellCompRequestCmd, p.Name} //nolint:prealloc // no need to over-complicate things.
cargs = append(cargs, args...)
cargs = append(cargs, toComplete)
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
}
+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.23
//go:build go1.25
package manager
@@ -28,7 +28,7 @@ func (e *pluginError) Unwrap() error {
return e.cause
}
// MarshalText marshalls the pluginError into a textual form.
// MarshalText marshals the pluginError into a textual form.
func (e *pluginError) MarshalText() (text []byte, err error) {
return []byte(e.cause.Error()), nil
}
+74 -37
View File
@@ -1,8 +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.25
package manager
import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"github.com/docker/cli/cli-plugins/hooks"
@@ -16,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
@@ -39,11 +41,11 @@ func RunCLICommandHooks(ctx context.Context, dockerCLI config.Provider, rootCmd,
// RunPluginHooks is the entrypoint for the hooks execution flow
// after a plugin command was just executed by the CLI.
func RunPluginHooks(ctx context.Context, dockerCLI config.Provider, rootCmd, subCommand *cobra.Command, args []string) {
func RunPluginHooks(ctx context.Context, dockerCLI config.Provider, rootCmd, subCommand *cobra.Command, args []string, cmdErrorMessage string) {
commandName := strings.Join(args, " ")
flags := getNaiveFlags(args)
runHooks(ctx, dockerCLI.ConfigFile(), rootCmd, subCommand, commandName, flags, "")
runHooks(ctx, dockerCLI.ConfigFile(), rootCmd, subCommand, commandName, flags, cmdErrorMessage)
}
func runHooks(ctx context.Context, cfg *configfile.ConfigFile, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) {
@@ -52,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
@@ -66,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)
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
@@ -135,14 +152,34 @@ func appendNextSteps(nextSteps []string, processed []string) ([]string, bool) {
// command being executed (such as 'image ls' the root 'docker' is omitted)
// and, if the configuration includes a hook for the invoked command, returns
// the configured hook string.
func pluginMatch(pluginCfg map[string]string, subCmd string) (string, bool) {
configuredPluginHooks, ok := pluginCfg["hooks"]
if !ok || configuredPluginHooks == "" {
//
// Plugins can declare two types of hooks in their configuration:
// - "hooks": fires on every command invocation (success or failure)
// - "error-hooks": fires only when a command fails (cmdErrorMessage is non-empty)
func pluginMatch(pluginCfg map[string]string, subCmd string, cmdErrorMessage string) (string, bool) {
// Check "hooks" first — these always fire regardless of command outcome.
if match, ok := matchHookConfig(pluginCfg["hooks"], subCmd); ok {
return match, true
}
// Check "error-hooks" — these only fire when there was an error.
if cmdErrorMessage != "" {
if match, ok := matchHookConfig(pluginCfg["error-hooks"], subCmd); ok {
return match, true
}
}
return "", false
}
// matchHookConfig checks if a comma-separated hook configuration string
// contains a prefix match for the given subcommand.
func matchHookConfig(configuredHooks string, subCmd string) (string, bool) {
if configuredHooks == "" {
return "", false
}
commands := strings.Split(configuredPluginHooks, ",")
for _, hookCmd := range commands {
for hookCmd := range strings.SplitSeq(configuredHooks, ",") {
if hookMatch(hookCmd, subCmd) {
return hookCmd, true
}
+239 -7
View File
@@ -1,12 +1,23 @@
package manager
import (
"context"
"testing"
"github.com/docker/cli/cli/config/configfile"
"github.com/spf13/cobra"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
type fakeConfigProvider struct {
cfg *configfile.ConfigFile
}
func (f *fakeConfigProvider) ConfigFile() *configfile.ConfigFile {
return f.cfg
}
func TestGetNaiveFlags(t *testing.T) {
testCases := []struct {
args []string
@@ -40,12 +51,15 @@ func TestGetNaiveFlags(t *testing.T) {
func TestPluginMatch(t *testing.T) {
testCases := []struct {
commandString string
pluginConfig map[string]string
expectedMatch string
expectedOk bool
doc string
commandString string
pluginConfig map[string]string
cmdErrorMessage string
expectedMatch string
expectedOk bool
}{
{
doc: "hooks prefix match",
commandString: "image ls",
pluginConfig: map[string]string{
"hooks": "image",
@@ -54,6 +68,7 @@ func TestPluginMatch(t *testing.T) {
expectedOk: true,
},
{
doc: "hooks no match",
commandString: "context ls",
pluginConfig: map[string]string{
"hooks": "build",
@@ -62,6 +77,7 @@ func TestPluginMatch(t *testing.T) {
expectedOk: false,
},
{
doc: "hooks exact match",
commandString: "context ls",
pluginConfig: map[string]string{
"hooks": "context ls",
@@ -70,6 +86,7 @@ func TestPluginMatch(t *testing.T) {
expectedOk: true,
},
{
doc: "hooks first match wins",
commandString: "image ls",
pluginConfig: map[string]string{
"hooks": "image ls,image",
@@ -78,6 +95,7 @@ func TestPluginMatch(t *testing.T) {
expectedOk: true,
},
{
doc: "hooks empty string",
commandString: "image ls",
pluginConfig: map[string]string{
"hooks": "",
@@ -86,6 +104,7 @@ func TestPluginMatch(t *testing.T) {
expectedOk: false,
},
{
doc: "hooks partial token no match",
commandString: "image inspect",
pluginConfig: map[string]string{
"hooks": "image i",
@@ -94,6 +113,7 @@ func TestPluginMatch(t *testing.T) {
expectedOk: false,
},
{
doc: "hooks prefix token match",
commandString: "image inspect",
pluginConfig: map[string]string{
"hooks": "image",
@@ -101,12 +121,140 @@ func TestPluginMatch(t *testing.T) {
expectedMatch: "image",
expectedOk: true,
},
{
doc: "error-hooks match on error",
commandString: "build",
pluginConfig: map[string]string{
"error-hooks": "build",
},
cmdErrorMessage: "exit status 1",
expectedMatch: "build",
expectedOk: true,
},
{
doc: "error-hooks no match on success",
commandString: "build",
pluginConfig: map[string]string{
"error-hooks": "build",
},
cmdErrorMessage: "",
expectedMatch: "",
expectedOk: false,
},
{
doc: "error-hooks prefix match on error",
commandString: "compose up",
pluginConfig: map[string]string{
"error-hooks": "compose",
},
cmdErrorMessage: "exit status 1",
expectedMatch: "compose",
expectedOk: true,
},
{
doc: "error-hooks no match for wrong command",
commandString: "pull",
pluginConfig: map[string]string{
"error-hooks": "build",
},
cmdErrorMessage: "exit status 1",
expectedMatch: "",
expectedOk: false,
},
{
doc: "hooks takes precedence over error-hooks",
commandString: "build",
pluginConfig: map[string]string{
"hooks": "build",
"error-hooks": "build",
},
cmdErrorMessage: "exit status 1",
expectedMatch: "build",
expectedOk: true,
},
{
doc: "hooks fires on success even with error-hooks configured",
commandString: "build",
pluginConfig: map[string]string{
"hooks": "build",
"error-hooks": "build",
},
cmdErrorMessage: "",
expectedMatch: "build",
expectedOk: true,
},
{
doc: "error-hooks with multiple commands",
commandString: "compose up",
pluginConfig: map[string]string{
"error-hooks": "build,compose up,pull",
},
cmdErrorMessage: "exit status 1",
expectedMatch: "compose up",
expectedOk: true,
},
}
for _, tc := range testCases {
match, ok := pluginMatch(tc.pluginConfig, tc.commandString)
assert.Equal(t, ok, tc.expectedOk)
assert.Equal(t, match, tc.expectedMatch)
t.Run(tc.doc, func(t *testing.T) {
match, ok := pluginMatch(tc.pluginConfig, tc.commandString, tc.cmdErrorMessage)
assert.Equal(t, ok, tc.expectedOk)
assert.Equal(t, match, tc.expectedMatch)
})
}
}
func TestMatchHookConfig(t *testing.T) {
testCases := []struct {
doc string
configuredHooks string
subCmd string
expectedMatch string
expectedOk bool
}{
{
doc: "empty config",
configuredHooks: "",
subCmd: "build",
expectedMatch: "",
expectedOk: false,
},
{
doc: "exact match",
configuredHooks: "build",
subCmd: "build",
expectedMatch: "build",
expectedOk: true,
},
{
doc: "prefix match",
configuredHooks: "image",
subCmd: "image ls",
expectedMatch: "image",
expectedOk: true,
},
{
doc: "comma-separated match",
configuredHooks: "pull,build,push",
subCmd: "build",
expectedMatch: "build",
expectedOk: true,
},
{
doc: "no match",
configuredHooks: "pull,push",
subCmd: "build",
expectedMatch: "",
expectedOk: false,
},
}
for _, tc := range testCases {
t.Run(tc.doc, func(t *testing.T) {
match, ok := matchHookConfig(tc.configuredHooks, tc.subCmd)
assert.Equal(t, ok, tc.expectedOk)
assert.Equal(t, match, tc.expectedMatch)
})
}
}
@@ -141,3 +289,87 @@ func TestAppendNextSteps(t *testing.T) {
})
}
}
func TestRunPluginHooksPassesErrorMessage(t *testing.T) {
cfg := configfile.New("")
cfg.Plugins = map[string]map[string]string{
"test-plugin": {"hooks": "build"},
}
provider := &fakeConfigProvider{cfg: cfg}
root := &cobra.Command{Use: "docker"}
sub := &cobra.Command{Use: "build"}
root.AddCommand(sub)
// Should not panic with empty error message (success case)
RunPluginHooks(context.Background(), provider, root, sub, []string{"build"}, "")
// Should not panic with non-empty error message (failure case)
RunPluginHooks(context.Background(), provider, root, sub, []string{"build"}, "exit status 1")
}
func TestRunPluginHooksErrorHooks(t *testing.T) {
cfg := configfile.New("")
cfg.Plugins = map[string]map[string]string{
"test-plugin": {"error-hooks": "build"},
}
provider := &fakeConfigProvider{cfg: cfg}
root := &cobra.Command{Use: "docker"}
sub := &cobra.Command{Use: "build"}
root.AddCommand(sub)
// Should not panic — error-hooks with error message
RunPluginHooks(context.Background(), provider, root, sub, []string{"build"}, "exit status 1")
// Should not panic — error-hooks with no error (should be skipped)
RunPluginHooks(context.Background(), provider, root, sub, []string{"build"}, "")
}
func TestInvokeAndCollectHooksErrorHooksSkippedOnSuccess(t *testing.T) {
cfg := configfile.New("")
cfg.Plugins = map[string]map[string]string{
"nonexistent": {"error-hooks": "build"},
}
root := &cobra.Command{Use: "docker"}
sub := &cobra.Command{Use: "build"}
root.AddCommand(sub)
// On success, error-hooks should not match, so the plugin
// binary is never looked up and no results are returned.
result := invokeAndCollectHooks(
context.Background(), cfg, root, sub,
"build", map[string]string{}, "",
)
assert.Check(t, is.Len(result, 0))
}
func TestInvokeAndCollectHooksNoPlugins(t *testing.T) {
cfg := configfile.New("")
root := &cobra.Command{Use: "docker"}
sub := &cobra.Command{Use: "build"}
root.AddCommand(sub)
result := invokeAndCollectHooks(
context.Background(), cfg, root, sub,
"build", map[string]string{}, "some error",
)
assert.Check(t, is.Len(result, 0))
}
func TestInvokeAndCollectHooksCancelledContext(t *testing.T) {
cfg := configfile.New("")
cfg.Plugins = map[string]map[string]string{
"test-plugin": {"hooks": "build"},
}
root := &cobra.Command{Use: "docker"}
sub := &cobra.Command{Use: "build"}
root.AddCommand(sub)
ctx, cancel := context.WithCancel(context.Background())
cancel() // cancel immediately
result := invokeAndCollectHooks(
ctx, cfg, root, sub,
"build", map[string]string{}, "exit status 1",
)
assert.Check(t, is.Nil(result))
}
+1 -1
View File
@@ -172,7 +172,7 @@ func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, e
}
// PluginRunCommand returns an [os/exec.Cmd] which when [os/exec.Cmd.Run] will execute the named plugin.
// The rootcmd argument is referenced to determine the set of builtin commands in order to detect conficts.
// The rootcmd argument is referenced to determine the set of builtin commands in order to detect conflicts.
// The error returned satisfies the [errdefs.IsNotFound] predicate if no plugin was found or if the first candidate plugin was invalid somehow.
func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Command) (*exec.Cmd, error) {
// This uses the full original args, not the args which may
+1 -1
View File
@@ -89,7 +89,7 @@ func TestListPluginCandidatesEmpty(t *testing.T) {
// Regression test for https://github.com/docker/cli/issues/5643.
// Check that inaccessible directories that come before accessible ones are ignored
// and do not prevent the latter from being processed.
func TestListPluginCandidatesInaccesibleDir(t *testing.T) {
func TestListPluginCandidatesInaccessibleDir(t *testing.T) {
dir := fs.NewDir(t, t.Name(),
fs.WithDir("no-perm", fs.WithMode(0)),
fs.WithDir("plugins",
-1
View File
@@ -16,6 +16,5 @@ import (
//
// [ConfigFile.CLIPluginsExtraDirs]: https://pkg.go.dev/github.com/docker/cli@v26.1.4+incompatible/cli/config/configfile#ConfigFile.CLIPluginsExtraDirs
var defaultSystemPluginDirs = []string{
filepath.Join(os.Getenv("ProgramData"), "Docker", "cli-plugins"),
filepath.Join(os.Getenv("ProgramFiles"), "Docker", "cli-plugins"),
}
+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.
+2
View File
@@ -33,4 +33,6 @@ type Metadata struct {
ShortDescription string `json:",omitempty"`
// URL is a pointer to the plugin's homepage.
URL string `json:",omitempty"`
// Hidden hides the plugin in completion and help message output.
Hidden bool `json:",omitempty"`
}
+1 -1
View File
@@ -93,7 +93,7 @@ func (pl *PluginServer) Addr() net.Addr {
// Close ensures that the server is no longer accepting new connections and
// closes all existing connections. Existing connections will receive [io.EOF].
//
// The error value is that of the underlying [net.Listner.Close] call.
// The error value is that of the underlying [net.Listener.Close] call.
func (pl *PluginServer) Close() error {
if pl == nil {
return nil
+4 -4
View File
@@ -47,16 +47,16 @@ func TestPluginServer(t *testing.T) {
select {
case err := <-done:
if !errors.Is(err, io.EOF) {
t.Fatalf("exepcted EOF error, got: %v", err)
t.Fatalf("expected EOF error, got: %v", err)
}
case <-time.After(10 * time.Millisecond):
}
})
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")
+6 -8
View File
@@ -250,11 +250,12 @@ func commandAliases(cmd *cobra.Command) string {
if cmd.HasParent() {
parentPath = cmd.Parent().CommandPath() + " "
}
aliases := cmd.CommandPath()
var aliases strings.Builder
aliases.WriteString(cmd.CommandPath())
for _, alias := range cmd.Aliases {
aliases += ", " + parentPath + alias
aliases.WriteString(", " + parentPath + alias)
}
return aliases
return aliases.String()
}
func topCommands(cmd *cobra.Command) []*cobra.Command {
@@ -350,13 +351,10 @@ func orchestratorSubCommands(cmd *cobra.Command) []*cobra.Command {
func allManagementSubCommands(cmd *cobra.Command) []*cobra.Command {
cmds := []*cobra.Command{}
for _, sub := range cmd.Commands() {
if isPlugin(sub) {
if invalidPluginReason(sub) == "" {
cmds = append(cmds, sub)
}
if invalidPluginReason(sub) != "" {
continue
}
if sub.IsAvailableCommand() && sub.HasSubCommands() {
if sub.IsAvailableCommand() && (isPlugin(sub) || sub.HasSubCommands()) {
cmds = append(cmds, sub)
}
}
+27
View File
@@ -56,6 +56,33 @@ func TestInvalidPlugin(t *testing.T) {
assert.DeepEqual(t, invalidPlugins(root), []*cobra.Command{sub1}, cmpopts.IgnoreUnexported(cobra.Command{}))
}
func TestHiddenPlugin(t *testing.T) {
root := &cobra.Command{Use: "root"}
sub1 := &cobra.Command{
Use: "sub1",
Hidden: true,
Annotations: map[string]string{
metadata.CommandAnnotationPlugin: "true",
},
Run: func(cmd *cobra.Command, args []string) {},
}
sub1sub1 := &cobra.Command{Use: "sub1sub1"}
sub1sub2 := &cobra.Command{Use: "sub1sub2"}
sub2 := &cobra.Command{
Use: "sub2",
Annotations: map[string]string{
metadata.CommandAnnotationPlugin: "true",
},
Run: func(cmd *cobra.Command, args []string) {},
}
root.AddCommand(sub1, sub2)
sub1.AddCommand(sub1sub1, sub1sub2)
assert.DeepEqual(t, allManagementSubCommands(root), []*cobra.Command{sub2}, cmpopts.IgnoreFields(cobra.Command{}, "Run"), cmpopts.IgnoreUnexported(cobra.Command{}))
}
func TestCommandAliases(t *testing.T) {
root := &cobra.Command{Use: "root"}
sub := &cobra.Command{Use: "subcommand", Aliases: []string{"alias1", "alias2"}}
+6 -6
View File
@@ -8,23 +8,23 @@ import (
type fakeClient struct {
client.Client
checkpointCreateFunc func(container string, options client.CheckpointCreateOptions) error
checkpointDeleteFunc func(container string, options client.CheckpointDeleteOptions) error
checkpointCreateFunc func(container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error)
checkpointDeleteFunc func(container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error)
checkpointListFunc func(container string, options client.CheckpointListOptions) (client.CheckpointListResult, error)
}
func (cli *fakeClient) CheckpointCreate(_ context.Context, container string, options client.CheckpointCreateOptions) error {
func (cli *fakeClient) CheckpointCreate(_ context.Context, container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error) {
if cli.checkpointCreateFunc != nil {
return cli.checkpointCreateFunc(container, options)
}
return nil
return client.CheckpointCreateResult{}, nil
}
func (cli *fakeClient) CheckpointDelete(_ context.Context, container string, options client.CheckpointDeleteOptions) error {
func (cli *fakeClient) CheckpointRemove(_ context.Context, container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error) {
if cli.checkpointDeleteFunc != nil {
return cli.checkpointDeleteFunc(container, options)
}
return nil
return client.CheckpointRemoveResult{}, nil
}
func (cli *fakeClient) CheckpointList(_ context.Context, container string, options client.CheckpointListOptions) (client.CheckpointListResult, error) {
+1 -1
View File
@@ -41,7 +41,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
}
func runCreate(ctx context.Context, dockerCLI command.Cli, opts createOptions) error {
err := dockerCLI.Client().CheckpointCreate(ctx, opts.container, client.CheckpointCreateOptions{
_, err := dockerCLI.Client().CheckpointCreate(ctx, opts.container, client.CheckpointCreateOptions{
CheckpointID: opts.checkpoint,
CheckpointDir: opts.checkpointDir,
Exit: !opts.leaveRunning,
+5 -5
View File
@@ -16,7 +16,7 @@ import (
func TestCheckpointCreateErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointCreateFunc func(container string, options client.CheckpointCreateOptions) error
checkpointCreateFunc func(container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error)
expectedError string
}{
{
@@ -29,8 +29,8 @@ func TestCheckpointCreateErrors(t *testing.T) {
},
{
args: []string{"foo", "bar"},
checkpointCreateFunc: func(container string, options client.CheckpointCreateOptions) error {
return errors.New("error creating checkpoint for container foo")
checkpointCreateFunc: func(container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error) {
return client.CheckpointCreateResult{}, errors.New("error creating checkpoint for container foo")
},
expectedError: "error creating checkpoint for container foo",
},
@@ -61,10 +61,10 @@ func TestCheckpointCreateWithOptions(t *testing.T) {
var actualContainerName string
var actualOptions client.CheckpointCreateOptions
cli := test.NewFakeCli(&fakeClient{
checkpointCreateFunc: func(container string, options client.CheckpointCreateOptions) error {
checkpointCreateFunc: func(container string, options client.CheckpointCreateOptions) (client.CheckpointCreateResult, error) {
actualContainerName = container
actualOptions = options
return nil
return client.CheckpointCreateResult{}, nil
},
})
cmd := newCreateCommand(cli)
+1 -1
View File
@@ -48,5 +48,5 @@ func runList(ctx context.Context, dockerCLI command.Cli, container string, opts
Output: dockerCLI.Out(),
Format: newFormat(formatter.TableFormatKey),
}
return formatWrite(cpCtx, checkpoints.Checkpoints)
return formatWrite(cpCtx, checkpoints.Items)
}
+2 -2
View File
@@ -55,7 +55,7 @@ func TestCheckpointListWithOptions(t *testing.T) {
containerID = container
checkpointDir = options.CheckpointDir
return client.CheckpointListResult{
Checkpoints: []checkpoint.Summary{
Items: []checkpoint.Summary{
{Name: "checkpoint-foo"},
},
}, nil
@@ -63,7 +63,7 @@ func TestCheckpointListWithOptions(t *testing.T) {
})
cmd := newListCommand(cli)
cmd.SetArgs([]string{"container-foo"})
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.Check(t, cmd.Flags().Set("checkpoint-dir", "/dir/foo"))
assert.NilError(t, cmd.Execute())
assert.Check(t, is.Equal("container-foo", containerID))
assert.Check(t, is.Equal("/dir/foo", checkpointDir))
+6 -10
View File
@@ -1,8 +1,6 @@
package checkpoint
import (
"context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/moby/moby/client"
@@ -22,7 +20,12 @@ func newRemoveCommand(dockerCLI command.Cli) *cobra.Command {
Short: "Remove a checkpoint",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return runRemove(cmd.Context(), dockerCLI, args[0], args[1], opts)
containerID, checkpointID := args[0], args[1]
_, err := dockerCLI.Client().CheckpointRemove(cmd.Context(), containerID, client.CheckpointRemoveOptions{
CheckpointID: checkpointID,
CheckpointDir: opts.checkpointDir,
})
return err
},
DisableFlagsInUseLine: true,
}
@@ -32,10 +35,3 @@ func newRemoveCommand(dockerCLI command.Cli) *cobra.Command {
return cmd
}
func runRemove(ctx context.Context, dockerCli command.Cli, container string, checkpointID string, opts removeOptions) error {
return dockerCli.Client().CheckpointDelete(ctx, container, client.CheckpointDeleteOptions{
CheckpointID: checkpointID,
CheckpointDir: opts.checkpointDir,
})
}
+6 -6
View File
@@ -14,7 +14,7 @@ import (
func TestCheckpointRemoveErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointDeleteFunc func(container string, options client.CheckpointDeleteOptions) error
checkpointDeleteFunc func(container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error)
expectedError string
}{
{
@@ -27,8 +27,8 @@ func TestCheckpointRemoveErrors(t *testing.T) {
},
{
args: []string{"foo", "bar"},
checkpointDeleteFunc: func(container string, options client.CheckpointDeleteOptions) error {
return errors.New("error deleting checkpoint")
checkpointDeleteFunc: func(container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error) {
return client.CheckpointRemoveResult{}, errors.New("error deleting checkpoint")
},
expectedError: "error deleting checkpoint",
},
@@ -49,16 +49,16 @@ func TestCheckpointRemoveErrors(t *testing.T) {
func TestCheckpointRemoveWithOptions(t *testing.T) {
var containerID, checkpointID, checkpointDir string
cli := test.NewFakeCli(&fakeClient{
checkpointDeleteFunc: func(container string, options client.CheckpointDeleteOptions) error {
checkpointDeleteFunc: func(container string, options client.CheckpointRemoveOptions) (client.CheckpointRemoveResult, error) {
containerID = container
checkpointID = options.CheckpointID
checkpointDir = options.CheckpointDir
return nil
return client.CheckpointRemoveResult{}, nil
},
})
cmd := newRemoveCommand(cli)
cmd.SetArgs([]string{"container-foo", "checkpoint-bar"})
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.Check(t, cmd.Flags().Set("checkpoint-dir", "/dir/foo"))
assert.NilError(t, cmd.Execute())
assert.Check(t, is.Equal("container-foo", containerID))
assert.Check(t, is.Equal("checkpoint-bar", checkpointID))
+3 -4
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.23
//go:build go1.25
package command
@@ -60,6 +60,7 @@ type Cli interface {
type DockerCli struct {
configFile *configfile.ConfigFile
options *cliflags.ClientOptions
clientOpts []client.Opt
in *streams.In
out *streams.Out
err *streams.Out
@@ -72,7 +73,6 @@ type DockerCli struct {
dockerEndpoint docker.Endpoint
contextStoreConfig *store.Config
initTimeout time.Duration
userAgent string
res telemetryResource
// baseCtx is the base context used for internal operations. In the future
@@ -533,8 +533,7 @@ func (cli *DockerCli) initialize() error {
return
}
if cli.client == nil {
ops := []client.Opt{client.WithUserAgent(cli.userAgent)}
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil {
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, cli.clientOpts...); cli.initErr != nil {
return
}
}
+11 -1
View File
@@ -104,6 +104,16 @@ func WithInitializeClient(makeClient func(*DockerCli) (client.APIClient, error))
}
}
// WithAPIClientOptions configures additional [client.Opt] to use when
// initializing the API client. These options have no effect if a custom
// client is set (through [WithAPIClient] or [WithInitializeClient]).
func WithAPIClientOptions(c ...client.Opt) CLIOption {
return func(cli *DockerCli) error {
cli.clientOpts = append(cli.clientOpts, c...)
return nil
}
}
// envOverrideHTTPHeaders is the name of the environment-variable that can be
// used to set custom HTTP headers to be sent by the client. This environment
// variable is the equivalent to the HttpHeaders field in the configuration
@@ -221,7 +231,7 @@ func WithUserAgent(userAgent string) CLIOption {
if userAgent == "" {
return errors.New("user agent cannot be blank")
}
cli.userAgent = userAgent
cli.clientOpts = append(cli.clientOpts, client.WithUserAgent(userAgent))
return nil
}
}
+26 -2
View File
@@ -120,8 +120,8 @@ func TestNewAPIClientFromFlagsWithCustomHeadersFromEnv(t *testing.T) {
}
func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
const customVersion = "v3.3.3"
const expectedVersion = "3.3.3"
const customVersion = "v3.3"
const expectedVersion = "3.3"
t.Setenv("DOCKER_API_VERSION", customVersion)
t.Setenv("DOCKER_HOST", ":2375")
@@ -358,6 +358,7 @@ func TestSetGoDebug(t *testing.T) {
assert.Equal(t, "val1,val2", os.Getenv("GODEBUG"))
})
t.Run("GODEBUG in context metadata can set env", func(t *testing.T) {
t.Setenv("GODEBUG", "")
meta := store.Metadata{
Metadata: DockerContext{
AdditionalFields: map[string]any{
@@ -392,3 +393,26 @@ func TestNewDockerCliWithCustomUserAgent(t *testing.T) {
assert.NilError(t, err)
assert.DeepEqual(t, received, "fake-agent/0.0.1")
}
func TestNewDockerCliWithAPIClientOptions(t *testing.T) {
var received string
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
received = r.UserAgent()
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()
host := strings.Replace(ts.URL, "http://", "tcp://", 1)
opts := &flags.ClientOptions{Hosts: []string{host}}
cli, err := NewDockerCli(
WithAPIClientOptions(client.WithUserAgent("fake-agent/0.0.1")),
)
assert.NilError(t, err)
cli.currentContext = DefaultContextName
cli.options = opts
cli.configFile = &configfile.ConfigFile{}
_, err = cli.Client().Ping(t.Context(), client.PingOptions{})
assert.NilError(t, err)
assert.DeepEqual(t, received, "fake-agent/0.0.1")
}
-1
View File
@@ -18,7 +18,6 @@ import (
_ "github.com/docker/cli/cli/command/stack"
_ "github.com/docker/cli/cli/command/swarm"
_ "github.com/docker/cli/cli/command/system"
_ "github.com/docker/cli/cli/command/trust"
_ "github.com/docker/cli/cli/command/volume"
"github.com/docker/cli/internal/commands"
"github.com/spf13/cobra"
+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.23
//go:build go1.25
package config
+8 -5
View File
@@ -70,6 +70,14 @@ func newAttachCommand(dockerCLI command.Cli) *cobra.Command {
// RunAttach executes an `attach` command
func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, opts *AttachOptions) error {
detachKeys := opts.DetachKeys
if detachKeys == "" {
detachKeys = dockerCLI.ConfigFile().DetachKeys
}
if err := validateDetachKeys(detachKeys); err != nil {
return err
}
apiClient := dockerCLI.Client()
// request channel to wait for client
@@ -85,11 +93,6 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
return err
}
detachKeys := dockerCLI.ConfigFile().DetachKeys
if opts.DetachKeys != "" {
detachKeys = opts.DetachKeys
}
options := client.ContainerAttachOptions{
Stream: true,
Stdin: !opts.NoStdin && c.Config.OpenStdin,
+8
View File
@@ -27,6 +27,14 @@ func TestNewAttachCommandErrors(t *testing.T) {
return client.ContainerInspectResult{}, errors.New("something went wrong")
},
},
{
name: "invalid-detach-keys",
args: []string{"--detach-keys", "shift-b", "5cb5bb5e4a3b"},
expectedError: "invalid detach keys (shift-b):",
containerInspectFunc: func(containerID string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{}, errors.New("something went wrong")
},
},
{
name: "client-stopped",
args: []string{"5cb5bb5e4a3b"},
+18 -21
View File
@@ -3,33 +3,18 @@ package container
import (
"context"
"io"
"reflect"
"net/http"
"strings"
"unsafe"
"github.com/moby/moby/client"
)
func mockContainerExportResult(content string) client.ContainerExportResult {
out := client.ContainerExportResult{}
// Set unexported field "rc"
v := reflect.ValueOf(&out).Elem()
f := v.FieldByName("rc")
r := io.NopCloser(strings.NewReader(content))
reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem().Set(reflect.ValueOf(r))
return out
return io.NopCloser(strings.NewReader(content))
}
func mockContainerLogsResult(content string) client.ContainerLogsResult {
out := client.ContainerLogsResult{}
// Set unexported field "rc"
v := reflect.ValueOf(&out).Elem()
f := v.FieldByName("rc")
r := io.NopCloser(strings.NewReader(content))
reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem().Set(reflect.ValueOf(r))
return out
return io.NopCloser(strings.NewReader(content))
}
type fakeStreamResult struct {
@@ -51,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)
@@ -143,11 +129,18 @@ 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)
}
return client.ContainerLogsResult{}, nil
return http.NoBody, nil
}
func (f *fakeClient) ClientVersion() string {
@@ -172,7 +165,7 @@ func (f *fakeClient) ContainerExport(_ context.Context, containerID string, _ cl
if f.containerExportFunc != nil {
return f.containerExportFunc(containerID)
}
return client.ContainerExportResult{}, nil
return http.NoBody, nil
}
func (f *fakeClient) ExecResize(_ context.Context, id string, options client.ExecResizeOptions) (client.ExecResizeResult, error) {
@@ -189,7 +182,7 @@ func (f *fakeClient) ContainerKill(ctx context.Context, containerID string, opti
return client.ContainerKillResult{}, nil
}
func (f *fakeClient) ContainersPrune(ctx context.Context, options client.ContainerPruneOptions) (client.ContainerPruneResult, error) {
func (f *fakeClient) ContainerPrune(ctx context.Context, options client.ContainerPruneOptions) (client.ContainerPruneResult, error) {
if f.containerPruneFunc != nil {
return f.containerPruneFunc(ctx, options)
}
@@ -247,3 +240,7 @@ func (f *fakeClient) ContainerPause(ctx context.Context, containerID string, opt
return client.ContainerPauseResult{}, nil
}
func (*fakeClient) Ping(_ context.Context, _ client.PingOptions) (client.PingResult, error) {
return client.PingResult{}, nil
}
+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.23
//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"))
}
+48 -70
View File
@@ -17,11 +17,9 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/types"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/jsonstream"
"github.com/docker/cli/opts"
"github.com/moby/moby/api/types/mount"
@@ -41,7 +39,6 @@ const (
type createOptions struct {
name string
platform string
untrusted bool
pull string // always, missing, never
quiet bool
useAPISocket bool
@@ -88,7 +85,9 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
_ = flags.SetAnnotation("platform", "version", []string{"1.32"})
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms())
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
// TODO(thaJeztah): DEPRECATED: remove in v29.1 or v30
flags.Bool("disable-content-trust", true, "Skip image verification (deprecated)")
_ = flags.MarkDeprecated("disable-content-trust", "support for docker content trust was removed")
copts = addFlags(flags)
addCompletions(cmd, dockerCLI)
@@ -96,14 +95,14 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
return cmd
}
func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error {
func runCreate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error {
if err := validatePullOpt(options.pull); err != nil {
return cli.StatusError{
Status: withHelp(err, "create").Error(),
StatusCode: 125,
}
}
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
proxyConfig := dockerCLI.ConfigFile().ParseProxyConfig(dockerCLI.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
newEnv := make([]string, 0, len(proxyConfig))
for k, v := range proxyConfig {
if v == nil {
@@ -113,24 +112,28 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
}
}
copts.env = *opts.NewListOptsRef(&newEnv, nil)
containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
serverInfo, err := dockerCLI.Client().Ping(ctx, client.PingOptions{})
if err != nil {
return err
}
containerCfg, err := parse(flags, copts, serverInfo.OSType)
if err != nil {
return cli.StatusError{
Status: withHelp(err, "create").Error(),
StatusCode: 125,
}
}
id, err := createContainer(ctx, dockerCli, containerCfg, options)
id, err := createContainer(ctx, dockerCLI, containerCfg, options)
if err != nil {
return err
}
_, _ = fmt.Fprintln(dockerCli.Out(), id)
_, _ = fmt.Fprintln(dockerCLI.Out(), id)
return nil
}
// FIXME(thaJeztah): this is the only code-path that uses APIClient.ImageCreate. Rewrite this to use the regular "pull" code (or vice-versa).
func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *createOptions) error {
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), img)
func pullImage(ctx context.Context, dockerCLI command.Cli, img string, options *createOptions) error {
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCLI.ConfigFile(), img)
if err != nil {
return err
}
@@ -140,7 +143,7 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
// Already validated.
ociPlatforms = append(ociPlatforms, platforms.MustParse(options.platform))
}
resp, err := dockerCli.Client().ImagePull(ctx, img, client.ImagePullOptions{
resp, err := dockerCLI.Client().ImagePull(ctx, img, client.ImagePullOptions{
RegistryAuth: encodedAuth,
Platforms: ociPlatforms,
})
@@ -151,7 +154,7 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
_ = resp.Close()
}()
out := dockerCli.Err()
out := dockerCLI.Err()
if options.quiet {
out = streams.NewOut(io.Discard)
}
@@ -173,8 +176,8 @@ func (cid *cidFile) Close() error {
if cid.written {
return nil
}
if err := os.Remove(cid.path); err != nil {
return fmt.Errorf("failed to remove the CID file '%s': %w", cid.path, err)
if err := os.Remove(cid.path); err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to remove the CID file: %w", err)
}
return nil
@@ -184,8 +187,8 @@ func (cid *cidFile) Write(id string) error {
if cid.file == nil {
return nil
}
if _, err := cid.file.Write([]byte(id)); err != nil {
return fmt.Errorf("failed to write the container ID to the file: %w", err)
if _, err := cid.file.WriteString(id); err != nil {
return fmt.Errorf("failed to write the container ID (%s) to file: %w", id, err)
}
cid.written = true
return nil
@@ -208,20 +211,16 @@ func newCIDFile(cidPath string) (*cidFile, error) {
}
//nolint:gocyclo
func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *containerConfig, options *createOptions) (containerID string, err error) {
func createContainer(ctx context.Context, dockerCLI command.Cli, containerCfg *containerConfig, options *createOptions) (containerID string, _ error) {
config := containerCfg.Config
hostConfig := containerCfg.HostConfig
networkingConfig := containerCfg.NetworkingConfig
var (
trustedRef reference.Canonical
namedRef reference.Named
)
var namedRef reference.Named
// TODO(thaJeztah): add a platform option-type / flag-type.
if options.platform != "" {
_, err = platforms.Parse(options.platform)
if err != nil {
if _, err := platforms.Parse(options.platform); err != nil {
return "", err
}
}
@@ -240,15 +239,6 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
}
if named, ok := ref.(reference.Named); ok {
namedRef = reference.TagNameOnly(named)
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && !options.untrusted {
var err error
trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef)
if err != nil {
return "", err
}
config.Image = reference.FamiliarString(trustedRef)
}
}
const dockerConfigPathInContainer = "/run/secrets/docker/config.json"
@@ -256,10 +246,11 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
if options.useAPISocket {
// We'll create two new mounts to handle this flag:
//
// 1. Mount the actual docker socket.
// 2. A synthezised ~/.docker/config.json with resolved tokens.
// 2. A synthesized ~/.docker/config.json with resolved tokens.
if dockerCli.ServerInfo().OSType == "windows" {
if dockerCLI.ServerInfo().OSType == "windows" {
return "", errors.New("flag --use-api-socket can't be used with a Windows Docker Engine")
}
@@ -294,18 +285,18 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
})
*/
var envvarPresent bool
for _, envvar := range containerCfg.Config.Env {
if strings.HasPrefix(envvar, "DOCKER_CONFIG=") {
envvarPresent = true
var envVarPresent bool
for _, envVar := range containerCfg.Config.Env {
if strings.HasPrefix(envVar, "DOCKER_CONFIG=") {
envVarPresent = true
}
}
// If the DOCKER_CONFIG env var is already present, we assume the client knows
// what they're doing and don't inject the creds.
if !envvarPresent {
if !envVarPresent {
// Resolve this here for later, ensuring we error our before we create the container.
creds, err := readCredentials(dockerCli)
creds, err := readCredentials(dockerCLI)
if err != nil {
return "", fmt.Errorf("resolving credentials failed: %w", err)
}
@@ -327,25 +318,15 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
platform = &p
}
pullAndTagImage := func() error {
if err := pullImage(ctx, dockerCli, config.Image, options); err != nil {
return err
}
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
return trust.TagTrusted(ctx, dockerCli.Client(), dockerCli.Err(), trustedRef, taggedRef)
}
return nil
}
if options.pull == PullImageAlways {
if err := pullAndTagImage(); err != nil {
if err := pullImage(ctx, dockerCLI, config.Image, options); err != nil {
return "", err
}
}
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCLI.Out().GetTtySize()
response, err := dockerCli.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
response, err := dockerCLI.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
Name: options.name,
// Image: config.Image, // TODO(thaJeztah): pass image-ref separate
Platform: platform,
@@ -358,15 +339,15 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
if errdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing {
if !options.quiet {
// we don't want to write to stdout anything apart from container.ID
_, _ = fmt.Fprintf(dockerCli.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
_, _ = fmt.Fprintf(dockerCLI.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
}
if err := pullAndTagImage(); err != nil {
if err := pullImage(ctx, dockerCLI, config.Image, options); err != nil {
return "", err
}
var retryErr error
response, retryErr = dockerCli.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
response, retryErr = dockerCLI.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
Name: options.name,
// Image: config.Image, // TODO(thaJeztah): pass image-ref separate
Platform: platform,
@@ -382,24 +363,20 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
}
}
containerID = response.ID
for _, w := range response.Warnings {
_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
}
err = containerIDFile.Write(containerID)
if options.useAPISocket && len(apiSocketCreds) > 0 {
// Create a new config file with just the auth.
newConfig := &configfile.ConfigFile{
if err := copyDockerConfigIntoContainer(ctx, dockerCLI.Client(), response.ID, dockerConfigPathInContainer, &configfile.ConfigFile{
AuthConfigs: apiSocketCreds,
}
if err := copyDockerConfigIntoContainer(ctx, dockerCli.Client(), containerID, dockerConfigPathInContainer, newConfig); err != nil {
return "", fmt.Errorf("injecting docker config.json into container failed: %w", err)
}); err != nil {
response.Warnings = append(response.Warnings, fmt.Sprintf("injecting docker config.json into container failed: %v", err))
}
}
for _, w := range response.Warnings {
_, _ = fmt.Fprintln(dockerCLI.Err(), "WARNING:", w)
}
return containerID, err
err = containerIDFile.Write(response.ID)
return response.ID, err
}
func validatePullOpt(val string) error {
@@ -439,6 +416,7 @@ func copyDockerConfigIntoContainer(ctx context.Context, apiClient client.APIClie
})
if _, err := io.Copy(tarWriter, &configBuf); err != nil {
_ = tarWriter.Close()
return fmt.Errorf("writing config to tar file for config copy: %w", err)
}
+27 -59
View File
@@ -5,6 +5,7 @@ import (
"errors"
"io"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
@@ -13,7 +14,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/notary"
"github.com/google/go-cmp/cmp"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/system"
@@ -43,17 +43,31 @@ func TestNewCIDFileWhenFileAlreadyExists(t *testing.T) {
}
func TestCIDFileCloseWithNoWrite(t *testing.T) {
tempdir := fs.NewDir(t, "test-cid-file")
defer tempdir.Remove()
// Closing should remove the file if it was not written to.
t.Run("closing should remove file", func(t *testing.T) {
filename := filepath.Join(t.TempDir(), "cidfile-1")
file, err := newCIDFile(filename)
assert.NilError(t, err)
assert.Check(t, is.Equal(file.path, filename))
path := tempdir.Join("cidfile")
file, err := newCIDFile(path)
assert.NilError(t, err)
assert.Check(t, is.Equal(file.path, path))
assert.NilError(t, file.Close())
_, err = os.Stat(filename)
assert.Check(t, os.IsNotExist(err))
})
assert.NilError(t, file.Close())
_, err = os.Stat(path)
assert.Check(t, os.IsNotExist(err))
// Closing (and removing) the file should not produce an error if the file no longer exists.
t.Run("close should remove file", func(t *testing.T) {
filename := filepath.Join(t.TempDir(), "cidfile-2")
file, err := newCIDFile(filename)
assert.NilError(t, err)
assert.Check(t, is.Equal(file.path, filename))
assert.NilError(t, os.Remove(filename))
_, err = os.Stat(filename)
assert.Check(t, os.IsNotExist(err))
assert.NilError(t, file.Close())
})
}
func TestCIDFileCloseWithWrite(t *testing.T) {
@@ -136,10 +150,9 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
}
fakeCLI := test.NewFakeCli(apiClient)
id, err := createContainer(context.Background(), fakeCLI, config, &createOptions{
name: "name",
platform: runtime.GOOS,
untrusted: true,
pull: tc.PullPolicy,
name: "name",
platform: runtime.GOOS,
pull: tc.PullPolicy,
})
if tc.ExpectedErrMsg != "" {
@@ -215,51 +228,6 @@ func TestCreateContainerValidateFlags(t *testing.T) {
}
}
func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
notaryFunc test.NotaryClientFuncType
}{
{
name: "offline-notary-server",
notaryFunc: notary.GetOfflineNotaryRepository,
expectedError: "client is offline",
args: []string{"image:tag"},
},
{
name: "uninitialized-notary-server",
notaryFunc: notary.GetUninitializedNotaryRepository,
expectedError: "remote trust data does not exist",
args: []string{"image:tag"},
},
{
name: "empty-notary-server",
notaryFunc: notary.GetEmptyTargetsNotaryRepository,
expectedError: "No valid trust data for tag",
args: []string{"image:tag"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("DOCKER_CONTENT_TRUST", "true")
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
return client.ContainerCreateResult{}, errors.New("shouldn't try to pull image")
},
})
fakeCLI.SetNotaryClient(tc.notaryFunc)
cmd := newCreateCommand(fakeCLI)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.ErrorContains(t, err, tc.expectedError)
})
}
}
func TestNewCreateCommandWithWarnings(t *testing.T) {
testCases := []struct {
name string
+15 -26
View File
@@ -101,7 +101,7 @@ func RunExec(ctx context.Context, dockerCLI command.Cli, containerIDorName strin
return err
}
if !options.Detach {
if err := dockerCLI.In().CheckTty(execOptions.AttachStdin, execOptions.Tty); err != nil {
if err := dockerCLI.In().CheckTty(execOptions.AttachStdin, execOptions.TTY); err != nil {
return err
}
}
@@ -119,17 +119,10 @@ func RunExec(ctx context.Context, dockerCLI command.Cli, containerIDorName strin
}
if options.Detach {
var cs client.ConsoleSize
if execOptions.ConsoleSize != nil {
cs = client.ConsoleSize{
Height: execOptions.ConsoleSize[0],
Width: execOptions.ConsoleSize[1],
}
}
_, err := apiClient.ExecStart(ctx, execID, client.ExecStartOptions{
Detach: options.Detach,
TTY: execOptions.Tty,
ConsoleSize: cs,
TTY: execOptions.TTY,
ConsoleSize: client.ConsoleSize{Height: execOptions.ConsoleSize.Height, Width: execOptions.ConsoleSize.Width},
})
return err
}
@@ -137,9 +130,9 @@ func RunExec(ctx context.Context, dockerCLI command.Cli, containerIDorName strin
}
func fillConsoleSize(execOptions *client.ExecCreateOptions, dockerCli command.Cli) {
if execOptions.Tty {
if execOptions.TTY {
height, width := dockerCli.Out().GetTtySize()
execOptions.ConsoleSize = &[2]uint{height, width}
execOptions.ConsoleSize = client.ConsoleSize{Height: height, Width: width}
}
}
@@ -157,7 +150,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *cl
out = dockerCli.Out()
}
if execOptions.AttachStderr {
if execOptions.Tty {
if execOptions.TTY {
stderr = dockerCli.Out()
} else {
stderr = dockerCli.Err()
@@ -166,16 +159,9 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *cl
fillConsoleSize(execOptions, dockerCli)
apiClient := dockerCli.Client()
var cs client.ConsoleSize
if execOptions.ConsoleSize != nil {
cs = client.ConsoleSize{
Height: execOptions.ConsoleSize[0],
Width: execOptions.ConsoleSize[1],
}
}
resp, err := apiClient.ExecAttach(ctx, execID, client.ExecAttachOptions{
TTY: execOptions.Tty,
ConsoleSize: cs,
TTY: execOptions.TTY,
ConsoleSize: client.ConsoleSize{Height: execOptions.ConsoleSize.Height, Width: execOptions.ConsoleSize.Width},
})
if err != nil {
return err
@@ -193,7 +179,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *cl
outputStream: out,
errorStream: stderr,
resp: resp.HijackedResponse,
tty: execOptions.Tty,
tty: execOptions.TTY,
detachKeys: execOptions.DetachKeys,
}
@@ -201,7 +187,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *cl
}()
}()
if execOptions.Tty && dockerCli.In().IsTerminal() {
if execOptions.TTY && dockerCli.In().IsTerminal() {
if err := MonitorTtySize(ctx, dockerCli, execID, true); err != nil {
_, _ = fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
}
@@ -215,7 +201,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *cl
return getExecExitStatus(ctx, apiClient, execID)
}
func getExecExitStatus(ctx context.Context, apiClient client.ContainerAPIClient, execID string) error {
func getExecExitStatus(ctx context.Context, apiClient client.ExecAPIClient, execID string) error {
resp, err := apiClient.ExecInspect(ctx, execID, client.ExecInspectOptions{})
if err != nil {
// If we can't connect, then the daemon probably died.
@@ -237,7 +223,7 @@ func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*client
execOptions := &client.ExecCreateOptions{
User: execOpts.User,
Privileged: execOpts.Privileged,
Tty: execOpts.TTY,
TTY: execOpts.TTY,
Cmd: execOpts.Command,
WorkingDir: execOpts.Workdir,
}
@@ -262,5 +248,8 @@ func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*client
} else {
execOptions.DetachKeys = configFile.DetachKeys
}
if err := validateDetachKeys(execOpts.DetachKeys); err != nil {
return nil, err
}
return execOptions, nil
}
+24 -14
View File
@@ -69,7 +69,7 @@ TWO=2
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: true,
TTY: true,
Cmd: []string{"command"},
},
},
@@ -86,27 +86,27 @@ TWO=2
Detach: true,
}),
expected: client.ExecCreateOptions{
Tty: true,
TTY: true,
Cmd: []string{"command"},
},
},
{
options: withDefaultOpts(ExecOptions{Detach: true}),
configFile: configfile.ConfigFile{DetachKeys: "de"},
configFile: configfile.ConfigFile{DetachKeys: "ctrl-d,e"},
expected: client.ExecCreateOptions{
Cmd: []string{"command"},
DetachKeys: "de",
DetachKeys: "ctrl-d,e",
},
},
{
options: withDefaultOpts(ExecOptions{
Detach: true,
DetachKeys: "ab",
DetachKeys: "ctrl-a,b",
}),
configFile: configfile.ConfigFile{DetachKeys: "de"},
configFile: configfile.ConfigFile{DetachKeys: "ctrl-d,e"},
expected: client.ExecCreateOptions{
Cmd: []string{"command"},
DetachKeys: "ab",
DetachKeys: "ctrl-a,b",
},
},
{
@@ -147,13 +147,23 @@ TWO=2
}
}
func TestParseExecNoSuchFile(t *testing.T) {
execOpts := withDefaultOpts(ExecOptions{})
assert.Check(t, execOpts.EnvFile.Set("no-such-env-file"))
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
assert.ErrorContains(t, err, "no-such-env-file")
assert.Check(t, os.IsNotExist(err))
assert.Check(t, execConfig == nil)
func TestParseExecErrors(t *testing.T) {
t.Run("missing env-file", func(t *testing.T) {
execOpts := withDefaultOpts(ExecOptions{})
assert.Check(t, execOpts.EnvFile.Set("no-such-env-file"))
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
assert.ErrorContains(t, err, "no-such-env-file")
assert.Check(t, os.IsNotExist(err))
assert.Check(t, execConfig == nil)
})
t.Run("invalid detach keys", func(t *testing.T) {
execOpts := withDefaultOpts(ExecOptions{
DetachKeys: "shift-a",
})
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
assert.Check(t, is.ErrorContains(err, "invalid detach keys (shift-a):"))
assert.Check(t, is.Nil(execConfig))
})
}
func TestRunExec(t *testing.T) {
+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
}
+18 -6
View File
@@ -30,6 +30,16 @@ func (r *readCloserWrapper) Close() error {
return r.closer()
}
func validateDetachKeys(keys string) error {
if keys == "" {
return nil
}
if _, err := term.ToBytes(keys); err != nil {
return invalidParameter(fmt.Errorf("invalid detach keys (%s): %w", keys, err))
}
return nil
}
// A hijackedIOStreamer handles copying input to and output from streams to the
// connection.
type hijackedIOStreamer struct {
@@ -82,13 +92,15 @@ func (h *hijackedIOStreamer) stream(ctx context.Context) error {
}
}
func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
func (h *hijackedIOStreamer) setupInput() (restore func(), _ error) {
if h.inputStream == nil || !h.tty {
// No need to setup input TTY.
// The restore func is a nop.
return func() {}, nil
}
if err := validateDetachKeys(h.detachKeys); err != nil {
return nil, err
}
if err := setRawTerminal(h.streams); err != nil {
return nil, fmt.Errorf("unable to set IO streams as raw terminal: %s", err)
}
@@ -103,11 +115,11 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
// Use default escape keys if an invalid sequence is given.
escapeKeys := defaultEscapeKeys
if h.detachKeys != "" {
customEscapeKeys, err := term.ToBytes(h.detachKeys)
var err error
escapeKeys, err = term.ToBytes(h.detachKeys)
if err != nil {
logrus.Warnf("invalid detach escape keys, using default: %s", err)
} else {
escapeKeys = customEscapeKeys
restore()
return nil, err
}
}
+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.23
//go:build go1.25
package container
+16 -18
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.23
// 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
@@ -426,6 +426,9 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
return nil, err
}
// short syntax ([ip:]public:private[/proto])
//
// TODO(thaJeztah): we need an equivalent that handles the "ip-address" part without depending on the nat package.
ports, natPortBindings, err := nat.ParsePortSpecs(convertedOpts)
if err != nil {
return nil, err
@@ -792,7 +795,7 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*network.EndpointSett
// and only a single network is specified, omit the endpoint-configuration
// on the client (the daemon will still create it when creating the container)
if i == 0 && len(copts.netMode.Value()) == 1 {
if ep == nil || reflect.DeepEqual(*ep, network.EndpointSettings{}) {
if ep == nil || reflect.ValueOf(*ep).IsZero() {
continue
}
}
@@ -899,7 +902,7 @@ func convertToStandardNotation(ports []string) ([]string, error) {
for _, publish := range ports {
if strings.Contains(publish, "=") {
params := map[string]string{"protocol": "tcp"}
for _, param := range strings.Split(publish, ",") {
for param := range strings.SplitSeq(publish, ",") {
k, v, ok := strings.Cut(param, "=")
if !ok || k == "" {
return optsList, fmt.Errorf("invalid publish opts format (should be name=value but got '%s')", param)
@@ -944,11 +947,11 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) {
if err != nil {
return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %w", v, err)
}
b := bytes.NewBuffer(nil)
if err := json.Compact(b, f); err != nil {
var b bytes.Buffer
if err := json.Compact(&b, f); err != nil {
return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %w", v, err)
}
securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
securityOpts[key] = "seccomp=" + b.String()
}
}
}
@@ -994,9 +997,11 @@ func parseDevice(device, serverOS string) (container.DeviceMapping, error) {
case "linux":
return parseLinuxDevice(device)
case "windows":
return parseWindowsDevice(device)
// Windows doesn't support mapping, so passing the given value as-is.
return container.DeviceMapping{PathOnHost: device}, nil
default:
return container.DeviceMapping{}, fmt.Errorf("unknown server OS: %s", serverOS)
}
return container.DeviceMapping{}, fmt.Errorf("unknown server OS: %s", serverOS)
}
// parseLinuxDevice parses a device mapping string to a container.DeviceMapping struct
@@ -1027,18 +1032,11 @@ func parseLinuxDevice(device string) (container.DeviceMapping, error) {
dst = src
}
deviceMapping := container.DeviceMapping{
return container.DeviceMapping{
PathOnHost: src,
PathInContainer: dst,
CgroupPermissions: permissions,
}
return deviceMapping, nil
}
// parseWindowsDevice parses a device mapping string to a container.DeviceMapping struct
// knowing that the target is a Windows daemon
func parseWindowsDevice(device string) (container.DeviceMapping, error) {
return container.DeviceMapping{PathOnHost: device}, nil
}, nil
}
// validateDeviceCgroupRule validates a device cgroup rule string format
+7 -5
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
@@ -61,7 +62,7 @@ func newPruneCommand(dockerCLI command.Cli) *cobra.Command {
const warning = `WARNING! This will remove all stopped containers.
Are you sure you want to continue?`
func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, _ error) {
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
if !options.force {
@@ -74,22 +75,23 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
}
}
res, err := dockerCli.Client().ContainersPrune(ctx, client.ContainerPruneOptions{
res, err := dockerCli.Client().ContainerPrune(ctx, client.ContainerPruneOptions{
Filters: pruneFilters,
})
if err != nil {
return 0, "", err
}
var out strings.Builder
if len(res.Report.ContainersDeleted) > 0 {
output = "Deleted Containers:\n"
out.WriteString("Deleted Containers:\n")
for _, id := range res.Report.ContainersDeleted {
output += id + "\n"
out.WriteString(id + "\n")
}
spaceReclaimed = res.Report.SpaceReclaimed
}
return spaceReclaimed, output, nil
return spaceReclaimed, out.String(), nil
}
type cancelledErr struct{ error }
+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,
}
+21 -11
View File
@@ -12,7 +12,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/opts"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
@@ -73,7 +72,10 @@ func newRunCommand(dockerCLI command.Cli) *cobra.Command {
// TODO(thaJeztah): consider adding platform as "image create option" on containerOptions
flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
_ = flags.SetAnnotation("platform", "version", []string{"1.32"})
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
// TODO(thaJeztah): DEPRECATED: remove in v29.1 or v30
flags.Bool("disable-content-trust", true, "Skip image verification (deprecated)")
_ = flags.MarkDeprecated("disable-content-trust", "support for docker content trust was removed")
copts = addFlags(flags)
_ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys)
@@ -82,14 +84,14 @@ func newRunCommand(dockerCLI command.Cli) *cobra.Command {
return cmd
}
func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
func runRun(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
if err := validatePullOpt(ropts.pull); err != nil {
return cli.StatusError{
Status: withHelp(err, "run").Error(),
StatusCode: 125,
}
}
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
proxyConfig := dockerCLI.ConfigFile().ParseProxyConfig(dockerCLI.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
newEnv := []string{}
for k, v := range proxyConfig {
if v == nil {
@@ -99,7 +101,12 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro
}
}
copts.env = *opts.NewListOptsRef(&newEnv, nil)
containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
serverInfo, err := dockerCLI.Client().Ping(ctx, client.PingOptions{})
if err != nil {
return err
}
containerCfg, err := parse(flags, copts, serverInfo.OSType)
// just in case the parse does not exit
if err != nil {
return cli.StatusError{
@@ -107,7 +114,7 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro
StatusCode: 125,
}
}
return runContainer(ctx, dockerCli, ropts, copts, containerCfg)
return runContainer(ctx, dockerCLI, ropts, copts, containerCfg)
}
//nolint:gocyclo
@@ -133,6 +140,14 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
config.StdinOnce = false
}
detachKeys := runOpts.detachKeys
if detachKeys == "" {
detachKeys = dockerCli.ConfigFile().DetachKeys
}
if err := validateDetachKeys(runOpts.detachKeys); err != nil {
return err
}
containerID, err := createContainer(ctx, dockerCli, containerCfg, &runOpts.createOptions)
if err != nil {
return toStatusError(err)
@@ -165,11 +180,6 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
}()
}
if attach {
detachKeys := dockerCli.ConfigFile().DetachKeys
if runOpts.detachKeys != "" {
detachKeys = runOpts.detachKeys
}
// ctx should not be cancellable here, as this would kill the stream to the container
// and we want to keep the stream open until the process in the container exits or until
// the user forcefully terminates the CLI.
+8 -62
View File
@@ -13,12 +13,9 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/notary"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/moby/moby/client/pkg/progress"
"github.com/moby/moby/client/pkg/streamformatter"
"github.com/spf13/pflag"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@@ -35,6 +32,11 @@ func TestRunValidateFlags(t *testing.T) {
args: []string{"--attach", "stdin", "--detach", "myimage"},
expectedErr: "conflicting options: cannot specify both --attach and --detach",
},
{
name: "with invalid --detach-keys",
args: []string{"--detach-keys", "shift-a", "myimage"},
expectedErr: "invalid detach keys (shift-a):",
},
} {
t.Run(tc.name, func(t *testing.T) {
cmd := newRunCommand(test.NewFakeCli(&fakeClient{}))
@@ -125,7 +127,7 @@ func TestRunAttach(t *testing.T) {
}
// end stream from "container" so that we'll detach
conn.Close()
assert.NilError(t, conn.Close())
select {
case cmdErr := <-cmdErrC:
@@ -205,7 +207,7 @@ func TestRunAttachTermination(t *testing.T) {
}
assert.NilError(t, syscall.Kill(syscall.Getpid(), syscall.SIGTERM))
conn.Close()
assert.NilError(t, conn.Close())
select {
case <-killCh:
@@ -241,20 +243,12 @@ func TestRunPullTermination(t *testing.T) {
_ = server.Close()
})
go func() {
id := test.RandomID()[:12] // short-ID
progressOutput := streamformatter.NewJSONProgressOutput(server, true)
for i := 0; i < 100; i++ {
for range 100 {
select {
case <-ctx.Done():
assert.NilError(t, server.Close(), "failed to close imageCreateFunc server")
return
default:
assert.NilError(t, progressOutput.WriteProgress(progress.Progress{
ID: id,
Message: "Downloading",
Current: int64(i),
Total: 100,
}))
time.Sleep(100 * time.Millisecond)
}
}
@@ -295,54 +289,6 @@ func TestRunPullTermination(t *testing.T) {
}
}
func TestRunCommandWithContentTrustErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
notaryFunc test.NotaryClientFuncType
}{
{
name: "offline-notary-server",
notaryFunc: notary.GetOfflineNotaryRepository,
expectedError: "client is offline",
args: []string{"image:tag"},
},
{
name: "uninitialized-notary-server",
notaryFunc: notary.GetUninitializedNotaryRepository,
expectedError: "remote trust data does not exist",
args: []string{"image:tag"},
},
{
name: "empty-notary-server",
notaryFunc: notary.GetEmptyTargetsNotaryRepository,
expectedError: "No valid trust data for tag",
args: []string{"image:tag"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("DOCKER_CONTENT_TRUST", "true")
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
return client.ContainerCreateResult{}, errors.New("shouldn't try to pull image")
},
})
fakeCLI.SetNotaryClient(tc.notaryFunc)
cmd := newRunCommand(fakeCLI)
cmd.SetArgs(tc.args)
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
err := cmd.Execute()
statusErr := cli.StatusError{}
assert.Check(t, errors.As(err, &statusErr))
assert.Check(t, is.Equal(statusErr.StatusCode, 125))
assert.Check(t, is.ErrorContains(err, tc.expectedError))
})
}
}
func TestRunContainerImagePullPolicyInvalid(t *testing.T) {
cases := []struct {
PullPolicy string
+1 -2
View File
@@ -11,8 +11,7 @@ import (
)
func TestForwardSignals(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := t.Context()
called := make(chan struct{})
apiClient := &fakeClient{containerKillFunc: func(ctx context.Context, container string, options client.ContainerKillOptions) (client.ContainerKillResult, error) {
+8 -5
View File
@@ -70,6 +70,14 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
ctx, cancelFun := context.WithCancel(ctx)
defer cancelFun()
detachKeys := opts.DetachKeys
if detachKeys == "" {
detachKeys = dockerCli.ConfigFile().DetachKeys
}
if err := validateDetachKeys(detachKeys); err != nil {
return err
}
switch {
case opts.Attach || opts.OpenStdin:
// We're going to attach to a container.
@@ -93,11 +101,6 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
defer signal.StopCatch(sigc)
}
detachKeys := dockerCli.ConfigFile().DetachKeys
if opts.DetachKeys != "" {
detachKeys = opts.DetachKeys
}
options := client.ContainerAttachOptions{
Stream: true,
Stdin: opts.OpenStdin && c.Container.Config.OpenStdin,
+38
View File
@@ -0,0 +1,38 @@
package container
import (
"io"
"testing"
"github.com/docker/cli/internal/test"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestStartValidateFlags(t *testing.T) {
for _, tc := range []struct {
name string
args []string
expectedErr string
}{
{
name: "with invalid --detach-keys",
args: []string{"--detach-keys", "shift-a", "myimage"},
expectedErr: "invalid detach keys (shift-a):",
},
} {
t.Run(tc.name, func(t *testing.T) {
cmd := newStartCommand(test.NewFakeCli(&fakeClient{}))
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
if tc.expectedErr != "" {
assert.Check(t, is.ErrorContains(err, tc.expectedErr))
} else {
assert.Check(t, is.Nil(err))
}
})
}
}
+71 -66
View File
@@ -1,12 +1,13 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.25
package container
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"strings"
"sync"
"time"
@@ -141,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(map[string]any{
"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(map[string]any{
"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(map[string]any{
"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).
@@ -213,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)
@@ -232,7 +226,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
for _, ctr := range cs.Items {
if s := NewStats(ctr.ID); cStats.add(s) {
waitFirst.Add(1)
log.G(ctx).WithFields(map[string]any{
log.G(ctx).WithFields(log.Fields{
"container": ctr.ID,
}).Debug("collecting stats for container")
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
@@ -255,7 +249,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
for _, ctr := range options.Containers {
if s := NewStats(ctr); cStats.add(s) {
waitFirst.Add(1)
log.G(ctx).WithFields(map[string]any{
log.G(ctx).WithFields(log.Fields{
"container": ctr,
}).Debug("collecting stats for container")
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
@@ -291,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
}
@@ -323,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.Split(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
@@ -366,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
+11 -2
View File
@@ -1,3 +1,6 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.25
package container
import (
@@ -14,8 +17,14 @@ import (
"github.com/sirupsen/logrus"
)
// TODO(thaJeztah): split resizeTTYTo
type resizeClient interface {
client.ExecAPIClient
client.ContainerAPIClient
}
// resizeTTYTo resizes TTY to specific height and width.
func resizeTTYTo(ctx context.Context, apiClient client.ContainerAPIClient, id string, height, width uint, isExec bool) error {
func resizeTTYTo(ctx context.Context, apiClient resizeClient, id string, height, width uint, isExec bool) error {
if height == 0 && width == 0 {
return nil
}
@@ -54,7 +63,7 @@ func initTtySize(ctx context.Context, cli command.Cli, id string, isExec bool, r
if err := rTTYfunc(ctx, cli, id, isExec); err != nil {
go func() {
var err error
for retry := 0; retry < 10; retry++ {
for retry := range 10 {
time.Sleep(time.Duration(retry+1) * 10 * time.Millisecond)
if err = rTTYfunc(ctx, cli, id, isExec); err == nil {
break
+3 -4
View File
@@ -1,11 +1,12 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.25
package command
import (
"encoding/json"
"errors"
"maps"
"github.com/docker/cli/cli/context/store"
)
@@ -23,9 +24,7 @@ func (dc DockerContext) MarshalJSON() ([]byte, error) {
s["Description"] = dc.Description
}
if dc.AdditionalFields != nil {
for k, v := range dc.AdditionalFields {
s[k] = v
}
maps.Copy(s, dc.AdditionalFields)
}
return json.Marshal(s)
}
+1 -1
View File
@@ -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.23
//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.23
//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.23
//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.23
//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.23
//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.23
//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.23
//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.23
//go:build go1.25
package command
+3 -3
View File
@@ -51,7 +51,7 @@ shared: {{.Shared}}
return Format(source)
}
func buildCacheSort(buildCache []*build.CacheRecord) {
func buildCacheSort(buildCache []build.CacheRecord) {
sort.Slice(buildCache, func(i, j int) bool {
lui, luj := buildCache[i].LastUsedAt, buildCache[j].LastUsedAt
switch {
@@ -70,7 +70,7 @@ func buildCacheSort(buildCache []*build.CacheRecord) {
}
// BuildCacheWrite renders the context for a list of containers
func BuildCacheWrite(ctx Context, buildCaches []*build.CacheRecord) error {
func BuildCacheWrite(ctx Context, buildCaches []build.CacheRecord) error {
render := func(format func(subContext SubContext) error) error {
buildCacheSort(buildCaches)
for _, bc := range buildCaches {
@@ -87,7 +87,7 @@ func BuildCacheWrite(ctx Context, buildCaches []*build.CacheRecord) error {
type buildCacheContext struct {
HeaderContext
trunc bool
v *build.CacheRecord
v build.CacheRecord
}
func newBuildCacheContext() *buildCacheContext {
+24 -13
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.23
//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
}
@@ -247,7 +258,7 @@ func (c *ContainerContext) Ports() string {
// State returns the container's current state (e.g. "running" or "paused").
// Refer to [container.ContainerState] for possible states.
func (c *ContainerContext) State() string {
return c.c.State
return string(c.c.State)
}
// Status returns the container's status in a human readable form (for example,
@@ -350,7 +361,7 @@ func DisplayablePorts(ports []container.PortSummary) string {
last uint16
}
groupMap := make(map[string]*portGroup)
var result []string //nolint:prealloc
var result []string
var hostMappings []string
var groupMapKeys []string
sort.Slice(ports, func(i, j int) bool {
+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.23
//go:build go1.25
package formatter
@@ -152,7 +152,7 @@ func TestContainerPsContext(t *testing.T) {
{
container: container.Summary{State: container.StateRunning},
trunc: true,
expValue: container.StateRunning,
expValue: string(container.StateRunning),
call: ctx.State,
},
{
+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.23
//go:build go1.25
package formatter
+90 -158
View File
@@ -12,14 +12,15 @@ import (
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/volume"
"github.com/moby/moby/client"
)
const (
defaultDiskUsageImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
defaultDiskUsageContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}"
defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
defaultDiskUsageBuildCacheTableFormat = "table {{.ID}}\t{{.CacheType}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}"
defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
defaultDiskUsageImageTableFormat Format = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
defaultDiskUsageContainerTableFormat Format = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}"
defaultDiskUsageVolumeTableFormat Format = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
defaultDiskUsageBuildCacheTableFormat Format = "table {{.ID}}\t{{.CacheType}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}"
defaultDiskUsageTableFormat Format = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
typeHeader = "TYPE"
totalHeader = "TOTAL"
@@ -33,20 +34,18 @@ const (
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
type DiskUsageContext struct {
Context
Verbose bool
LayersSize int64
Images []*image.Summary
Containers []*container.Summary
Volumes []*volume.Volume
BuildCache []*build.CacheRecord
BuilderSize int64
Verbose bool
ImageDiskUsage client.ImagesDiskUsage
BuildCacheDiskUsage client.BuildCacheDiskUsage
ContainerDiskUsage client.ContainersDiskUsage
VolumeDiskUsage client.VolumesDiskUsage
}
func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, error) {
func (ctx *DiskUsageContext) startSubsection(format Format) (*template.Template, error) {
ctx.buffer = &bytes.Buffer{}
ctx.header = ""
ctx.Format = Format(format)
ctx.preFormat()
ctx.Format = format
return ctx.parseFormat()
}
@@ -69,7 +68,7 @@ func NewDiskUsageFormat(source string, verbose bool) Format {
{{end -}}`
return format
case !verbose && source == TableFormatKey:
return Format(defaultDiskUsageTableFormat)
return defaultDiskUsageTableFormat
case !verbose && source == RawFormatKey:
format := `type: {{.Type}}
total: {{.TotalCount}}
@@ -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 {
@@ -96,35 +94,49 @@ func (ctx *DiskUsageContext) Write() (err error) {
}
err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
totalSize: ctx.LayersSize,
images: ctx.Images,
totalCount: ctx.ImageDiskUsage.TotalCount,
activeCount: ctx.ImageDiskUsage.ActiveCount,
totalSize: ctx.ImageDiskUsage.TotalSize,
reclaimable: ctx.ImageDiskUsage.Reclaimable,
images: ctx.ImageDiskUsage.Items,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
containers: ctx.Containers,
totalCount: ctx.ContainerDiskUsage.TotalCount,
activeCount: ctx.ContainerDiskUsage.ActiveCount,
totalSize: ctx.ContainerDiskUsage.TotalSize,
reclaimable: ctx.ContainerDiskUsage.Reclaimable,
containers: ctx.ContainerDiskUsage.Items,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
volumes: ctx.Volumes,
totalCount: ctx.VolumeDiskUsage.TotalCount,
activeCount: ctx.VolumeDiskUsage.ActiveCount,
totalSize: ctx.VolumeDiskUsage.TotalSize,
reclaimable: ctx.VolumeDiskUsage.Reclaimable,
volumes: ctx.VolumeDiskUsage.Items,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageBuilderContext{
builderSize: ctx.BuilderSize,
buildCache: ctx.BuildCache,
totalCount: ctx.BuildCacheDiskUsage.TotalCount,
activeCount: ctx.BuildCacheDiskUsage.ActiveCount,
builderSize: ctx.BuildCacheDiskUsage.TotalSize,
reclaimable: ctx.BuildCacheDiskUsage.Reclaimable,
buildCache: ctx.BuildCacheDiskUsage.Items,
})
if err != nil {
return err
}
diskUsageContainersCtx := diskUsageContainersContext{containers: []*container.Summary{}}
diskUsageContainersCtx := diskUsageContainersContext{containers: []container.Summary{}}
diskUsageContainersCtx.Header = SubHeaderContext{
"Type": typeHeader,
"TotalCount": totalHeader,
@@ -146,18 +158,18 @@ type diskUsageContext struct {
func (ctx *DiskUsageContext) verboseWrite() error {
duc := &diskUsageContext{
Images: make([]*imageContext, 0, len(ctx.Images)),
Containers: make([]*ContainerContext, 0, len(ctx.Containers)),
Volumes: make([]*volumeContext, 0, len(ctx.Volumes)),
BuildCache: make([]*buildCacheContext, 0, len(ctx.BuildCache)),
Images: make([]*imageContext, 0, len(ctx.ImageDiskUsage.Items)),
Containers: make([]*ContainerContext, 0, len(ctx.ContainerDiskUsage.Items)),
Volumes: make([]*volumeContext, 0, len(ctx.VolumeDiskUsage.Items)),
BuildCache: make([]*buildCacheContext, 0, len(ctx.BuildCacheDiskUsage.Items)),
}
trunc := ctx.Format.IsTable()
// First images
for _, i := range ctx.Images {
for _, i := range ctx.ImageDiskUsage.Items {
repo := "<none>"
tag := "<none>"
if len(i.RepoTags) > 0 && !isDangling(*i) {
if len(i.RepoTags) > 0 && !isDangling(i) {
// Only show the first tag
ref, err := reference.ParseNormalizedNamed(i.RepoTags[0])
if err != nil {
@@ -173,25 +185,25 @@ func (ctx *DiskUsageContext) verboseWrite() error {
repo: repo,
tag: tag,
trunc: trunc,
i: *i,
i: i,
})
}
// Now containers
for _, c := range ctx.Containers {
for _, c := range ctx.ContainerDiskUsage.Items {
// Don't display the virtual size
c.SizeRootFs = 0
duc.Containers = append(duc.Containers, &ContainerContext{trunc: trunc, c: *c})
duc.Containers = append(duc.Containers, &ContainerContext{trunc: trunc, c: c})
}
// And volumes
for _, v := range ctx.Volumes {
duc.Volumes = append(duc.Volumes, &volumeContext{v: *v})
for _, v := range ctx.VolumeDiskUsage.Items {
duc.Volumes = append(duc.Volumes, &volumeContext{v: v})
}
// And build cache
buildCacheSort(ctx.BuildCache)
for _, v := range ctx.BuildCache {
buildCacheSort(ctx.BuildCacheDiskUsage.Items)
for _, v := range ctx.BuildCacheDiskUsage.Items {
duc.BuildCache = append(duc.BuildCache, &buildCacheContext{v: v, trunc: trunc})
}
@@ -199,7 +211,6 @@ func (ctx *DiskUsageContext) verboseWrite() error {
return ctx.verboseWriteTable(duc)
}
ctx.preFormat()
tmpl, err := ctx.parseFormat()
if err != nil {
return err
@@ -212,7 +223,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
if err != nil {
return err
}
ctx.Output.Write([]byte("Images space usage:\n\n"))
_, _ = ctx.Output.Write([]byte("Images space usage:\n\n"))
for _, img := range duc.Images {
if err := ctx.contextFormat(tmpl, img); err != nil {
return err
@@ -224,7 +235,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
if err != nil {
return err
}
ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
_, _ = ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
for _, c := range duc.Containers {
if err := ctx.contextFormat(tmpl, c); err != nil {
return err
@@ -248,7 +259,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
if err != nil {
return err
}
_, _ = fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
_, _ = fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuildCacheDiskUsage.TotalSize)))
for _, v := range duc.BuildCache {
if err := ctx.contextFormat(tmpl, v); err != nil {
return err
@@ -261,8 +272,11 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
type diskUsageImagesContext struct {
HeaderContext
totalSize int64
images []*image.Summary
totalSize int64
reclaimable int64
totalCount int64
activeCount int64
images []image.Summary
}
func (c *diskUsageImagesContext) MarshalJSON() ([]byte, error) {
@@ -274,18 +288,11 @@ func (*diskUsageImagesContext) Type() string {
}
func (c *diskUsageImagesContext) TotalCount() string {
return strconv.Itoa(len(c.images))
return strconv.FormatInt(c.totalCount, 10)
}
func (c *diskUsageImagesContext) Active() string {
used := 0
for _, i := range c.images {
if i.Containers > 0 {
used++
}
}
return strconv.Itoa(used)
return strconv.FormatInt(c.activeCount, 10)
}
func (c *diskUsageImagesContext) Size() string {
@@ -293,27 +300,19 @@ func (c *diskUsageImagesContext) Size() string {
}
func (c *diskUsageImagesContext) Reclaimable() string {
var used int64
for _, i := range c.images {
if i.Containers != 0 {
if i.Size == -1 || i.SharedSize == -1 {
continue
}
used += i.Size - i.SharedSize
}
}
reclaimable := c.totalSize - used
if c.totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/c.totalSize)
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(c.reclaimable)), (c.reclaimable*100)/c.totalSize)
}
return units.HumanSize(float64(reclaimable))
return units.HumanSize(float64(c.reclaimable))
}
type diskUsageContainersContext struct {
HeaderContext
containers []*container.Summary
totalCount int64
activeCount int64
totalSize int64
reclaimable int64
containers []container.Summary
}
func (c *diskUsageContainersContext) MarshalJSON() ([]byte, error) {
@@ -325,62 +324,32 @@ func (*diskUsageContainersContext) Type() string {
}
func (c *diskUsageContainersContext) TotalCount() string {
return strconv.Itoa(len(c.containers))
}
func (*diskUsageContainersContext) isActive(ctr container.Summary) bool {
switch ctr.State {
case container.StateRunning, container.StatePaused, container.StateRestarting:
return true
case container.StateCreated, container.StateRemoving, container.StateExited, container.StateDead:
return false
default:
// Unknown state (should never happen).
return false
}
return strconv.FormatInt(c.totalCount, 10)
}
func (c *diskUsageContainersContext) Active() string {
used := 0
for _, ctr := range c.containers {
if c.isActive(*ctr) {
used++
}
}
return strconv.Itoa(used)
return strconv.FormatInt(c.activeCount, 10)
}
func (c *diskUsageContainersContext) Size() string {
var size int64
for _, ctr := range c.containers {
size += ctr.SizeRw
}
return units.HumanSize(float64(size))
return units.HumanSize(float64(c.totalSize))
}
func (c *diskUsageContainersContext) Reclaimable() string {
var reclaimable, totalSize int64
for _, ctr := range c.containers {
if !c.isActive(*ctr) {
reclaimable += ctr.SizeRw
}
totalSize += ctr.SizeRw
if c.totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(c.reclaimable)), (c.reclaimable*100)/c.totalSize)
}
if totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
}
return units.HumanSize(float64(reclaimable))
return units.HumanSize(float64(c.reclaimable))
}
type diskUsageVolumesContext struct {
HeaderContext
volumes []*volume.Volume
totalCount int64
activeCount int64
totalSize int64
reclaimable int64
volumes []volume.Volume
}
func (c *diskUsageVolumesContext) MarshalJSON() ([]byte, error) {
@@ -392,56 +361,32 @@ func (*diskUsageVolumesContext) Type() string {
}
func (c *diskUsageVolumesContext) TotalCount() string {
return strconv.Itoa(len(c.volumes))
return strconv.FormatInt(c.totalCount, 10)
}
func (c *diskUsageVolumesContext) Active() string {
used := 0
for _, v := range c.volumes {
if v.UsageData.RefCount > 0 {
used++
}
}
return strconv.Itoa(used)
return strconv.FormatInt(c.activeCount, 10)
}
func (c *diskUsageVolumesContext) Size() string {
var size int64
for _, v := range c.volumes {
if v.UsageData.Size != -1 {
size += v.UsageData.Size
}
}
return units.HumanSize(float64(size))
return units.HumanSize(float64(c.totalSize))
}
func (c *diskUsageVolumesContext) Reclaimable() string {
var reclaimable int64
var totalSize int64
for _, v := range c.volumes {
if v.UsageData.Size != -1 {
if v.UsageData.RefCount == 0 {
reclaimable += v.UsageData.Size
}
totalSize += v.UsageData.Size
}
if c.totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(c.reclaimable)), (c.reclaimable*100)/c.totalSize)
}
if totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
}
return units.HumanSize(float64(reclaimable))
return units.HumanSize(float64(c.reclaimable))
}
type diskUsageBuilderContext struct {
HeaderContext
totalCount int64
activeCount int64
builderSize int64
buildCache []*build.CacheRecord
reclaimable int64
buildCache []build.CacheRecord
}
func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
@@ -453,17 +398,11 @@ func (*diskUsageBuilderContext) Type() string {
}
func (c *diskUsageBuilderContext) TotalCount() string {
return strconv.Itoa(len(c.buildCache))
return strconv.FormatInt(c.totalCount, 10)
}
func (c *diskUsageBuilderContext) Active() string {
numActive := 0
for _, bc := range c.buildCache {
if bc.InUse {
numActive++
}
}
return strconv.Itoa(numActive)
return strconv.FormatInt(c.activeCount, 10)
}
func (c *diskUsageBuilderContext) Size() string {
@@ -471,12 +410,5 @@ func (c *diskUsageBuilderContext) Size() string {
}
func (c *diskUsageBuilderContext) Reclaimable() string {
var inUseBytes int64
for _, bc := range c.buildCache {
if bc.InUse && !bc.Shared {
inUseBytes += bc.Size
}
}
return units.HumanSize(float64(c.builderSize - inUseBytes))
return units.HumanSize(float64(c.reclaimable))
}
+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.23
//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.23
//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.23
//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.23
//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() {
+14 -18
View File
@@ -1,11 +1,13 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.25
package formatter
import (
"reflect"
"testing"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
type dummy struct{}
@@ -45,24 +47,18 @@ var dummyExpected = map[string]any{
func TestMarshalMap(t *testing.T) {
d := dummy{}
m, err := marshalMap(&d)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(dummyExpected, m) {
t.Fatalf("expected %+v, got %+v",
dummyExpected, m)
}
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(m, dummyExpected))
}
func TestMarshalMapBad(t *testing.T) {
if _, err := marshalMap(nil); err == nil {
t.Fatal("expected an error (argument is nil)")
}
if _, err := marshalMap(dummy{}); err == nil {
t.Fatal("expected an error (argument is non-pointer)")
}
_, err := marshalMap(nil)
assert.Check(t, is.Error(err, "expected a pointer to a struct, got invalid"), "expected an error (argument is nil)")
_, err = marshalMap(dummy{})
assert.Check(t, is.Error(err, "expected a pointer to a struct, got struct"), "expected an error (argument is non-pointer)")
x := 42
if _, err := marshalMap(&x); err == nil {
t.Fatal("expected an error (argument is a pointer to non-struct)")
}
_, err = marshalMap(&x)
assert.Check(t, is.Error(err, "expected a pointer to a struct, got a pointer to int"), "expected an error (argument is a pointer to non-struct)")
}
+1 -1
View File
@@ -12,7 +12,7 @@
// based on https://github.com/golang/go/blob/master/src/text/tabwriter/tabwriter.go Last modified 690ac40 on 31 Jan
//nolint:gocyclo,gofumpt,nakedret,unused // ignore linting errors, so that we can stick close to upstream
//nolint:gocyclo,nakedret,unused // ignore linting errors, so that we can stick close to upstream
package tabwriter
import (
@@ -25,7 +25,7 @@ func (b *buffer) Write(buf []byte) (written int, err error) {
m := len(buf)
if n+m <= cap(b.a) {
b.a = b.a[0 : n+m]
for i := 0; i < m; i++ {
for i := range m {
b.a[n+i] = buf[i]
}
} else {
@@ -669,7 +669,7 @@ func BenchmarkTable(b *testing.B) {
for i := 0; i < b.N; i++ {
w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings
// Write the line h times.
for j := 0; j < h; j++ {
for range h {
w.Write(line)
}
w.Flush()
@@ -681,7 +681,7 @@ func BenchmarkTable(b *testing.B) {
w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings
for i := 0; i < b.N; i++ {
// Write the line h times.
for j := 0; j < h; j++ {
for range h {
w.Write(line)
}
w.Flush()
@@ -701,7 +701,7 @@ func BenchmarkPyramid(b *testing.B) {
for i := 0; i < b.N; i++ {
w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings
// Write increasing prefixes of that line.
for j := 0; j < x; j++ {
for j := range x {
w.Write(line[:j*2])
w.Write([]byte{'\n'})
}
@@ -723,7 +723,7 @@ func BenchmarkRagged(b *testing.B) {
for i := 0; i < b.N; i++ {
w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings
// Write the lines in turn h times.
for j := 0; j < h; j++ {
for j := range h {
w.Write(lines[j%len(lines)])
w.Write([]byte{'\n'})
}
+1 -1
View File
@@ -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.23
//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.23
//go:build go1.25
package idresolver
+1 -1
View File
@@ -60,7 +60,7 @@ func TestResolveWithCache(t *testing.T) {
idResolver := New(apiClient, false)
ctx := context.Background()
for i := 0; i < 2; i++ {
for range 2 {
id, err := idResolver.Resolve(ctx, swarm.Node{}, "nodeID")
assert.NilError(t, err)
assert.Check(t, is.Equal("node-foo", id))
+2 -1
View File
@@ -149,8 +149,9 @@ func newBuildCommand(dockerCLI command.Cli) *cobra.Command {
flags.SetAnnotation("target", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/buildx/build/#target"})
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
// TODO(thaJeztah): DEPRECATED: remove in v29.1 or v30
flags.Bool("disable-content-trust", true, "Skip image verification (deprecated)")
_ = flags.MarkHidden("disable-content-trust")
_ = flags.MarkDeprecated("disable-content-trust", "support for docker content trust was removed")
flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
flags.SetAnnotation("platform", "version", []string{"1.38"})
+1 -1
View File
@@ -480,7 +480,7 @@ func Compress(buildCtx io.ReadCloser) (io.ReadCloser, error) {
pipeReader, pipeWriter := io.Pipe()
go func() {
compressWriter, err := compression.CompressStream(pipeWriter, archive.Gzip)
compressWriter, err := compression.CompressStream(pipeWriter, compression.Gzip)
if err != nil {
_ = pipeWriter.CloseWithError(err)
}

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