Compare commits

...

117 Commits

Author SHA1 Message Date
30df2d0b3a fix(loader): Allows multiple protocols on one port
Some checks failed
build / prepare (push) Has been cancelled
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 25, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 25, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
test / host (macos-15) (push) Has been cancelled
test / host (macos-15-intel) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
build / build (push) Has been cancelled
build / plugins (push) Has been cancelled
2026-02-02 12:28:16 +01:00
ecc694264d Merge pull request #6621 from thaJeztah/28.x_plugin_hide
Some checks failed
build / prepare (push) Has been cancelled
build / build (push) Has been cancelled
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 25, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 25, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
test / host (macos-15) (push) Has been cancelled
test / host (macos-15-intel) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
[28.x backport] Plugin may set itself as hidden
2025-11-05 11:32:31 +01:00
c475c696cf Plugin may set itself as hidden
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
(cherry picked from commit 700875b666)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 07:33:23 +01:00
7494d2cee4 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>
(cherry picked from commit 259df25a96)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-05 07:33:08 +01:00
5306df36fa Merge pull request #6610 from thaJeztah/28.x_backport_deprecate_builder_utils
[28.x backport] cli/command/image/build: deprecate `DefaultDockerfileName`, `DetectArchiveReader`, `WriteTempDockerfile`, `ResolveAndValidateContextPath`
2025-11-04 07:23:51 -06:00
5dbaa52325 cli/command/image/build: deprecate ResolveAndValidateContextPath util
This utility was used internally and will be removed in the next release.
Use `DetectContextType` to detect the context-type, and use `GetContextFromLocalDir`,
`GetContextFromLocalDir`, `GetContextFromGitURL`, or `GetContextFromURL`
instead.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 0f2f9e9c41)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-04 07:14:44 -06:00
dd832b6d97 cli/command/image/build: deprecate WriteTempDockerfile util
It was only used internal in the package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 6e1ff0bec1)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-04 07:14:44 -06:00
a99e91cc89 cli/command/image/build: deprecate DetectArchiveReader util
It was only used internal in the package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit c52fa073cd)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-04 07:14:44 -06:00
579b72aa06 cli/command/image/build: deprecate DefaultDockerfileName const
It was only used internal in the package.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit f24bb4bc76)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-04 07:14:40 -06:00
6fc5891770 Merge pull request #6560 from thaJeztah/28.x_backport_deprecations
[28.x backport] opts, image/build: deprecate IsArchive, ValidateMACAddress,  ListOpts.Delete()
2025-11-04 07:12:50 -06:00
9af6cbc489 Merge pull request #6613 from thaJeztah/28.x_bump_go
[28.x] update to go1.24.9
2025-11-04 06:45:14 -06:00
ba53322412 update to go1.24.9
go1.24.9 (released 2025-10-13) includes fixes to the crypto/x509 package.
See the Go 1.24.9 milestone on our issue tracker for details:

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-04 11:10:08 +01:00
61d2e8aaad Merge pull request #6609 from thaJeztah/28.x_backport_bump_golangci_lint
[28.x backport] Dockerfile: update golangci-lint to v2.5.0
2025-11-03 11:39:06 -06:00
6abcd4a2a1 Merge pull request #6608 from thaJeztah/28.x_backport_update_gotestsum
[28.x backport] Dockerfile: bump gotest.tools/gotestsum v1.13.0
2025-11-03 15:41:42 +01:00
00c129b974 Merge pull request #6607 from thaJeztah/28.x_backport_update_actions
[28.x backport] update actions
2025-11-03 15:33:29 +01:00
fbf5c8a86d Dockerfile: update golangci-lint to v2.5.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 5ad9fbdef7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-03 12:45:13 +01:00
4c7d52534f Dockerfile: bump gotest.tools/gotestsum v1.13.0
full diff: https://github.com/gotestyourself/gotestsum/compare/v1.12.3...v1.13.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit f8b1b8d165)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-03 12:37:13 +01:00
70123c523c build(deps): bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit aef2ef8c77)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-03 12:32:03 +01:00
4942b2747f build(deps): bump github/codeql-action from 3 to 4
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 5483b10e94)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-03 12:31:44 +01:00
d8bab71747 opts: deprecate ListOpts.Delete()
This method was added as part of a refactor in [moby@1ba1138], at which
time it was used to delete original values for "--host" and "--volume"
after normalizing. This beccame redundant in [moby@6200002], which added
specialized options that used a validate function, which both validated
and normalized inputs.

It's no longer used, so let's mark it deprecated so that we can remove it.

[moby@1ba1138]: 1ba11384bf
[moby@6200002]: 6200002669

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 193db8ec41)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 14:33:08 +02:00
36d9523e31 opts: deprecate ValidateMACAddress
It was a wrapper around net.ParseMAC from stdlib, so users should
use that directly.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 17d6a92954)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 14:30:34 +02:00
7063a0ef07 cli/command/image: runBuild: inline vars and minor cleanups
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9e646f6d92)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 14:26:53 +02:00
22487ad6f6 cli/command/image/build: deprecate IsArchive utility
It was only used internally.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 64be664e85)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 14:24:44 +02:00
4737ed4906 cli/command/image/build: fix linting, add sub-tests
- fix minor linting issues (unhandled errors)
- rename vars to prevent shadowing
- use sub-tests for tests that already prepared for it

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 2c539a6530)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 14:24:30 +02:00
8cff0087da Merge pull request #6544 from thaJeztah/28.x_bump_engine
[28.x] vendor: github.com/docker/docker v28.5.1
2025-10-09 10:11:06 +02:00
e8b22ce03c vendor: github.com/docker/docker v28.5.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-08 16:17:13 +02:00
e180ab8ab8 Merge pull request #6541 from vvoland/6540-28.x
Some checks failed
build / prepare (push) Has been cancelled
build / build (push) Has been cancelled
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 25, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 25, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
test / host (macos-15) (push) Has been cancelled
test / host (macos-15-intel) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
[28.x backport] update to go1.24.8
2025-10-07 19:50:32 -07:00
0d799c556f update to go1.24.8
This minor release includes 10 security fixes following the security policy:

- net/mail: excessive CPU consumption in ParseAddress

    The ParseAddress function constructed domain-literal address components through repeated string concatenation. When parsing large domain-literal components, this could cause excessive CPU consumption.

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

    This is CVE-2025-61725 and Go issue https://go.dev/issue/75680.

- crypto/x509: quadratic complexity when checking name constraints

    Due to the design of the name constraint checking algorithm, the processing time
    of some inputs scales non-linearly with respect to the size of the certificate.

    This affects programs which validate arbitrary certificate chains.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-58187 and Go issue https://go.dev/issue/75681.

- crypto/tls: ALPN negotiation errors can contain arbitrary text

    The crypto/tls conn.Handshake method returns an error on the server-side when
    ALPN negotation fails which can contain arbitrary attacker controlled
    information provided by the client-side of the connection which is not escaped.

    This affects programs which log these errors without any additional form of
    sanitization, and may allow injection of attacker controlled information into
    logs.

    Thanks to National Cyber Security Centre Finland for reporting this issue.

    This is CVE-2025-58189 and Go issue https://go.dev/issue/75652.

- encoding/pem: quadratic complexity when parsing some invalid inputs

    Due to the design of the PEM parsing function, the processing time for some
    inputs scales non-linearly with respect to the size of the input.

    This affects programs which parse untrusted PEM inputs.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-61723 and Go issue https://go.dev/issue/75676.

- net/url: insufficient validation of bracketed IPv6 hostnames

    The Parse function permitted values other than IPv6 addresses to be included in square brackets within the host component of a URL. RFC 3986 permits IPv6 addresses to be included within the host component, enclosed within square brackets. For example: "http://[::1]/". IPv4 addresses and hostnames must not appear within square brackets. Parse did not enforce this requirement.

    Thanks to Enze Wang, Jingcheng Yang and Zehui Miao of Tsinghua University for reporting this issue.

    This is CVE-2025-47912 and Go issue https://go.dev/issue/75678.

- encoding/asn1: pre-allocating memory when parsing DER payload can cause memory exhaustion

    When parsing DER payloads, memories were being allocated prior to fully validating the payloads.
    This permits an attacker to craft a big empty DER payload to cause memory exhaustion in functions such as asn1.Unmarshal, x509.ParseCertificateRequest, and ocsp.ParseResponse.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-58185 and Go issue https://go.dev/issue/75671.

- net/http: lack of limit when parsing cookies can cause memory exhaustion

    Despite HTTP headers having a default limit of 1 MB, the number of cookies that can be parsed did not have a limit.
    By sending a lot of very small cookies such as "a=;", an attacker can make an HTTP server allocate a large amount of structs, causing large memory consumption.

    net/http now limits the number of cookies accepted to 3000, which can be adjusted using the httpcookiemaxnum GODEBUG option.

    Thanks to jub0bs for reporting this issue.

    This is CVE-2025-58186 and Go issue https://go.dev/issue/75672.

- crypto/x509: panic when validating certificates with DSA public keys

    Validating certificate chains which contain DSA public keys can cause programs
    to panic, due to a interface cast that assumes they implement the Equal method.

    This affects programs which validate arbitrary certificate chains.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-58188 and Go issue https://go.dev/issue/75675.

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

    tar.Reader did not set a maximum size on the number of sparse region data blocks in GNU tar pax 1.0 sparse files. A maliciously-crafted archive containing a large number of sparse regions could cause a Reader to read an unbounded amount of data from the archive into memory. When reading from a compressed source, a small compressed input could result in large allocations.

    Thanks to Harshit Gupta (Mr HAX) - https://www.linkedin.com/in/iam-harshit-gupta/ for reporting this issue.

    This is CVE-2025-58183 and Go issue https://go.dev/issue/75677.

- net/textproto: excessive CPU consumption in Reader.ReadResponse

    The Reader.ReadResponse function constructed a response string through
    repeated string concatenation of lines. When the number of lines in a response is large,
    this could cause excessive CPU consumption.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-61724 and Go issue https://go.dev/issue/75716.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit e598ea0176)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-10-07 21:51:36 +02:00
887030fbe8 Merge pull request #6522 from thaJeztah/28.x_bump_moby
Some checks failed
build / prepare (push) Has been cancelled
build / build (push) Has been cancelled
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 25, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 25, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
test / host (macos-15) (push) Has been cancelled
test / host (macos-15-intel) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
[28.x] vendor: github.com/docker/docker cd048300a487 (v28.5.0-dev)
2025-10-02 14:36:11 +00:00
9c6a0e0ba9 Merge pull request #6531 from thaJeztah/28.x_backport_bump_macos
[28.x backport] gha: add macOS 15, remove macOS 13 (deprecated)
2025-10-02 14:35:37 +00:00
f784471104 vendor: github.com/docker/docker cd048300a487 (v28.5.0-dev)
full diff: https://github.com/moby/moby/compare/v28.5.0-rc.1...cd048300a48700294339c9c91d2dcc691cb8f63b

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 17:34:28 +02:00
d7afcf9b98 Merge pull request #6529 from thaJeztah/28.x_backport_deprecate_ResolveDefaultContext
[28.x backport] cli/command: deprecate ResolveDefaultContext
2025-10-01 16:33:50 +02:00
9d9adf6346 gha: add macOS 15, remove macOS 13 (deprecated)
The macOS 13 runners are deprecated and will be removed on December 4th,
with brownouts in November;
https://github.blog/changelog/2025-09-19-github-actions-macos-13-runner-image-is-closing-down/

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 91d8c0bf62)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 12:05:21 +02:00
d4b7734f18 cli/command: deprecate ResolveDefaultContext
The ResolveDefaultContext function was exported in [cli@f820766] to allow
(unit) testing, but did not document that it was only exported for this
purpose. The only external use of this function is in buildx, which uses
it in a unit test that can be implemented without this function.

This patch deprecates the function so that we can remove it.

[cli@f820766]: f820766f6a

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 311a97a210)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 11:49:22 +02:00
a1061611fd Merge pull request #6519 from thaJeztah/28.x_backport_authconfig_no_direct_cast
[28.x backport] cli/command: explicitly map AuthConfig fields instead of a direct cast
2025-09-29 15:55:27 -07:00
5e42f826b4 Merge pull request #6518 from thaJeztah/28.x_backport_memstore_notfounderr
[28.x backport] cli/config/memorystore: remove unused IsErrValueNotFound
2025-09-29 15:13:54 -07:00
645c23bd13 Merge pull request #6514 from thaJeztah/28.x_backport_manifeststore_notfound
[28.x backport] cli/manifest/store: deprecate IsNotFound
2025-09-29 15:11:41 -07:00
e491078fc6 cli/command: explicitly map AuthConfig fields instead of a direct cast
Commit [cli@27b2797] forked the AuthConfig type from the API, and changed
existing code to do a direct cast / convert of the forked type to the API
type. This can cause issues if the API types diverges, such as the removal
of the Email field.

This patch explicitly maps each field to the corresponding API type, but
adds some TODOs, because various code-paths only included a subset of the
fields, which may be intentional for fields that were meant to be handled
on the daemon / registry-client only.

We should evaluate these conversions to make sure these fields should
be sent from the client or not (and possibly even removed from the API
type).

[cli@27b2797]: 27b2797f7d

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9f02d9643d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-29 13:15:05 +02:00
a3ffb8a148 cli/config/memorystore: remove unused IsErrValueNotFound
This utility was added in 9b83d5bbf9, but
was never used. Remove the utility, and rewrite the error returned to
implement the errdefs.NotFound interface, so that it can be detected
using the errdefs.IsNotFound() utility if needed.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 3c78ac2aad)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-29 13:08:43 +02:00
a4ae5f2f7a cli/manifest/store: deprecate IsNotFound
Deprecate the IsNotFound utility in favor of errdefs.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit f3fb7728c7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-26 23:35:20 +02:00
394991e2ab Merge pull request #6510 from thaJeztah/28.x_backport_auth
[28.x backport] cli/command/image: pushTrustedReference: internalize constructing indexInfo
2025-09-26 08:14:49 -07:00
e5bce5cd2d Merge pull request #6509 from thaJeztah/28.x_backport_dct_retiring
[28.x backport] trust: print deprecation warning when using hub Notary server
2025-09-26 08:13:53 -07:00
d5c181abf4 cli/command/image: pushTrustedReference: internalize constructing indexInfo
All information needed can be deducted from the image reference, which
is used to create a indexInfo, repoInfo, and to resolve auth-config.

In some situations this may result in resolving the auth-config twice
after it already was resolved to an encoded auth-config.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9a6313ed3b)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-26 11:29:44 +02:00
0351ece9e5 trust: print deprecation warning when using hub Notary server
Docker Hub's Notary service is being retired, and now produces
failures in most cases. Add a warning when attempting to use
it, pending full removal of trust;
https://www.docker.com/blog/retiring-docker-content-trust/

With this PR:

    DOCKER_CONTENT_TRUST=1 docker pull -q hello-world
    WARNING: Docker is retiring DCT for Docker Official Images (DOI).
             For details, refer to https://docs.docker.com/go/dct-deprecation/

    could not validate the path to a trusted root: unable to retrieve valid leaf certificates

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 43b03ef2c5)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-26 11:24:04 +02:00
ec00b85794 cli/command/image: runPush: minor cleanups and linting issues
- Remove redundant intermediate variables
- Explicitly use an early return on error instead of combining with
  other checks.
- Fix unhandled errors and combine defers
- Remove outstanding TODO that unlikely will be addressed

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit c36e67d7b6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-26 11:21:59 +02:00
a69c591c5a Merge pull request #6501 from thaJeztah/28.x_bump_docker_28.5
[28.x] vendor: github.com/docker/docker v28.5.0-rc.1
2025-09-25 17:33:08 +02:00
f9d2820a20 vendor: github.com/docker/docker v28.5.0-rc.1
full diff: https://github.com/docker/docker/compare/d21856f25dbe...v28.5.0-rc.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 15:50:30 +02:00
c28ec0e4ce Merge pull request #6497 from thaJeztah/28.x_backport_deprecate_apply
Some checks failed
build / prepare (push) Has been cancelled
build / build (push) Has been cancelled
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 25, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 25, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 25, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-13) (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
[28.x backport] cli/command: deprecate DockerCli.Apply
2025-09-25 12:29:30 +00:00
d1c8336395 cli/command: deprecate DockerCli.Apply
The Apply method was added when CLI options for constructing the CLI were
rewritten into functional options in [cli@7f207f3]. There was no mention
in the pull request of this method specifically, and this may have been
related to work being done elsewhere on compose-on-kubernetes or the
compose-cli plugin that may have needed options to modify the CLI config
after it was already initialized.

The CLI itself no longer depends on this method since [cli@133279f], and
the only known consumer (docker compose) no longer needs it since [cli@2711800]
and [cli@048e931].

This patch deprecates the method with the intent to remove it in a future
release.

[cli@7f207f3]: 7f207f3f95
[cli@133279f]: 133279fb0d
[cli@2711800]: 2711800430
[cli@048e931]: 048e931b42

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 24bfedf3f8)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 13:30:15 +02:00
61b9fd4068 Merge pull request #6491 from thaJeztah/28.x_backport_deprecate_defaultversion
[28.x backport] cli/command: deprecate DockerCli.DefaultVersion
2025-09-25 11:25:04 +00:00
2ef1b4eabe Merge pull request #6495 from thaJeztah/28.x_backport_deprecate_ContentTrustEnabled
[28.x backport] cli/command: deprecate DockerCli.ContentTrustEnabled
2025-09-25 11:24:29 +00:00
bea31fccbb Merge pull request #6489 from thaJeztah/28.x_backport_deprecate_dct_opts
[28.x backport] cli/command: deprecate WithContentTrustFromEnv, WithContentTrust
2025-09-25 11:23:41 +00:00
e9c189e1c2 cli/command: deprecate DockerCli.DefaultVersion
This function was used internally, but is no longer used. There are
no known users of this method, so already removing it from the Cli
interface.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 0270b2d6f7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 12:58:46 +02:00
118548d02b cli/command: deprecate DockerCli.ContentTrustEnabled
This function was used internally, but is no longer used. Users should check
the value of the `DOCKER_CONTENT_TRUST` environment variable instead.

There are no known external users of this method, so already removing it
from the Cli interface; this method will be removed in the next release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 11d40488dd)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 12:55:31 +02:00
026ef0df2d [28.x] remove remaining uses of DockerCli.ContentTrustEnabled
These were already replaced for stubs in master.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 12:55:26 +02:00
4665091715 cli/command: deprecate WithContentTrustFromEnv, WithContentTrust
These options were used internally as defaults for the constructor and
only impact commands implemented in the CLI itself.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 40cdfc0d81)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 12:52:28 +02:00
261d8bcf8d trust: add internal utility for checking DOCKER_CONTENT_TRUST
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 1bae6aafa8)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 12:50:30 +02:00
3755161455 Merge pull request #6493 from thaJeztah/28.x_backport_fix_alpine
[28.x backport] e2e: update openssh, openssl to work around openssh bug
2025-09-25 11:41:45 +02:00
db5a0ae673 e2e: update openssh, openssl to work around openssh bug
relates to https://gitlab.alpinelinux.org/alpine/aports/-/issues/17547

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b611f288ee)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-25 10:59:32 +02:00
843153da37 Merge pull request #6477 from thaJeztah/28.x_backport_cli_user_agent
[28.x backport] cli/command: add WithUserAgent option
2025-09-24 10:36:10 -07:00
a2d7989230 Merge pull request #6483 from austinvazquez/vendor-docker-docker-28.x
[28.x] vendor: github.com/docker/docker v28.5.0-dev
2025-09-24 19:19:30 +02:00
985cee2de0 cli/command: add WithUserAgent option
Add support to the `cli/command` package to accept a custom User
Agent to pass to the underlying client.

This is used as the `UpstreamClient` portion of the `User-Agent`
when the Moby daemon makes requests.

For example, pushing and pulling images with Compose might result
in the registry seeing a `User-Agent` value of:

```
docker/24.0.7 go/go1.20.10 git-commit/311b9ff kernel/6.5.13-linuxkit os/linux arch/arm64 UpstreamClient(docker-cli-plugin-compose/v2.24.0)
```

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 048e931b42)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-09-24 19:18:04 +02:00
e1dd0e1501 Merge pull request #6481 from thaJeztah/28.x_backport_cli_plugin_options
[28.x backport] cli-plugins/plugin: Run: allow customizing the CLI
2025-09-24 19:10:17 +02:00
82ff4b5634 Merge pull request #6482 from thaJeztah/28.x_backport_plugin_semverish
[28.x backport] cli-plugins/manager: allow schema-versions <= 2.0.0
2025-09-24 19:09:27 +02:00
4de56bc72f vendor: github.com/docker/docker v28.5.0-dev
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
2025-09-24 11:39:14 -05:00
2ed0d99acc cli-plugins/manager: allow schema-versions <= 2.0.0
The CLI currently hard-codes the schema-version for CLI plugins to
"0.1.0", which doesn't allow us to expand the schema for plugins.

As there's many plugins that we shipped already, we can't break
compatibility until we reach 2.0.0, but we can expand the schema
with non-breaking changes.

This patch makes the validation more permissive to allow new schema
versions <= 2.0.0. Note that existing CLIs will still invalidate
such versions, so we cannot update the version until such CLIs are
no longer expected to be used, but this patch lays the ground-work
to open that option.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ec912e5524)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 11:09:58 -05:00
cbeddb1390 Merge pull request #6480 from thaJeztah/28.x_backport_remove_cli_experimental_remnants
[28.x backport] remove some remnants from CLI "experimental" config option
2025-09-24 18:06:20 +02:00
1fb1577626 cli-plugins/plugin: Run: allow customizing the CLI
Currently, the plugin.Run command constructs the DockerCli using
the default options, assuming plugins run with all the same options
as the CLI itself; to customize the CLI there's a "Apply" option,
but this means mutating the CLI after it's already constructed, which
is not ideal.

This patch adds a variadic ops argument to allow CLI plugins to pass
custom options to use for the CLI, so that there's no need to mutate
its config in most cases.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 2711800430)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 16:24:39 +02:00
67885d0dcc Merge pull request #6474 from thaJeztah/28.x_backport_bump_go_connections
[28.x backport] vendor: github.com/docker/go-connections v0.6.0
2025-09-24 07:20:43 -07:00
2ed42a8ade cli-plugins/manager: reformat TestValidateCandidate table
Slightly more verbose, but makes it easier to see properties
of each test.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 057f3128b6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 16:20:11 +02:00
4bac500fb2 remove some remnants from CLI "experimental" config option
Experimental is always enabled (977d3ae046),
and the `Experimental` field in plugin metadata was deprecated in
977d3ae046 and removed in commit
6a50c4f700.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit dfbac70efa)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 16:20:11 +02:00
9d9f632527 Merge pull request #6476 from thaJeztah/28.x_backport_ignore_broken_symlinks
[28.x backport] cli-plugins/manager: ignore broken symlinks
2025-09-24 07:18:05 -07:00
a2e17eb9d5 Merge pull request #6478 from thaJeztah/28.x_backport_cli_plugins_touchup
[28.x backport] cli-plugins/plugin: Run: touch-up godoc and minor cleanups
2025-09-24 07:13:18 -07:00
5d201ca436 cli-plugins/plugin: Run: touch-up godoc and minor cleanups
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 635a718209)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 15:17:50 +02:00
d1122a2293 cli-plugins/manager: ignore broken symlinks
Before this patch, a broken symlink would print a warning;

    docker info > /dev/null
    WARNING: Plugin "/Users/thajeztah/.docker/cli-plugins/docker-feedback" is not valid: failed to fetch metadata: fork/exec /Users/thajeztah/.docker/cli-plugins/docker-feedback: no such file or directory

After this patch, such symlinks are ignored:

    docker info > /dev/null

With debug enabled, we don't ignore the faulty plugin, which will
make the warning shown on docker info;

    mkdir -p ~/.docker/cli-plugins
    ln -s nosuchplugin ~/.docker/cli-plugins/docker-brokenplugin
    docker --debug info
    Client:
     Version:    29.0.0-dev
     Context:    default
     Debug Mode: true
     Plugins:
      buildx: Docker Buildx (Docker Inc.)
        Version:  v0.25.0
        Path:     /usr/libexec/docker/cli-plugins/docker-buildx
    WARNING: Plugin "/Users/thajeztah/.docker/cli-plugins/docker-brokenplugin" is not valid: failed to fetch metadata: fork/exec /Users/thajeztah/.docker/cli-plugins/docker-brokenplugin: no such file or directory

    # ...

We should als consider passing a "seen" map to de-duplicate entries.
Entries can be either a direct symlink or in a symlinked path (for
which we can filepath.EvalSymlinks). We need to benchmark the overhead
of resolving the symlink vs possibly calling the plugin (to get their
metadata) further down the line.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9b2f831452)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 14:34:44 +02:00
d52de77ef4 vendor: github.com/docker/go-connections v0.6.0
- deprecate sockets.GetProxyEnv, sockets.DialerFromEnvironment
- add support for unix sockets on Windows
- remove legacy CBC cipher suites from client config
- align client and server defaults to be the same.
- remove support for encrypted TLS private keys.
- nat: optimize ParsePortSpec

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 3529651fa7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 13:59:33 +02:00
01f8949484 Merge pull request #6472 from thaJeztah/28.x_backport_remove_special_handlings
[28.x backport] remove special handling for plugin errors and Windows warning on Build
2025-09-24 13:56:45 +02:00
b9e3346b1e Merge pull request #6473 from thaJeztah/28.x_backport_deprecation_preparations
[28.x backport] remove uses of client options that will be deprecated
2025-09-24 13:54:43 +02:00
25421ace0b Merge pull request #6471 from thaJeztah/28.x_backport_fix_stats_bounds
[28.x backport] cli/command/container: prevent panic during stats on empty event Actor.ID
2025-09-24 13:50:25 +02:00
ede7019b14 Merge pull request #6470 from thaJeztah/28.x_backport_setup-go-6
[28.x backport] CI updates
2025-09-24 13:50:09 +02:00
d48256b26d Merge pull request #6469 from thaJeztah/28.x_backport_template_deprecate_newparse
[28.x backport] templates: deprecate NewParse()
2025-09-24 13:44:06 +02:00
09fcf8e3dd cli/command: NewDockerCli: don't depend on DockerCli.Apply
The Apply method was added when CLI options for constructing the CLI were
rewritten into functional options in [cli@7f207f3]. There was no mention
in the pull request of this method specifically, and this may have been
related to work being done elsewhere on compose-on-kubernetes or the
compose-cli plugin that may have needed options to modify the CLI config
after it was already initialized.

We should try to remove functions that mutate the CLI configuration after
initialization if possible (and likely remove the `Apply` method); currently
this function is used in docker compose, but as part of a hack that can
probably be avoided.

[cli@7f207f3]: 7f207f3f95

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 133279fb0d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 13:37:57 +02:00
56f7bd0759 Set ReservedSpace field in preparation of KeepStorage deprecation
This change updates the builder prune command to send the `ReservedSpace` parameter in preparation of `KeepStorage` deprecation in API v1.52.

Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
(cherry picked from commit 7d85d8fbea)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 13:37:45 +02:00
3bacc99580 cli/command/image: build: remove permissions warning on Windows
This warning was added in [moby@4a8b3ca] to print a warning when building
Linux images from a Windows client. Window's filesystem does not have an
"executable" bit, which mean that, for example, copying a shell script
to an image during build would lose the executable bit. So for Windows
clients, the executable bit would be set on all files, unconditionally.

Originally this was detected in the client, which had direct access to
the API response headers, but when refactoring the client to use a common
library in [moby@535c4c9], this was refactored into a `ImageBuildResponse`
wrapper, deconstructing the API response into an `io.Reader` and a string
field containing only the `OSType` header.

This was the only use and only purpose of the `OSType` field, and now that
BuildKit is the default builder for Linux images, this warning didn't get
printed unless BuildKit was explicitly disabled.

This patch removes the warning, so that we can potentially remove the
field, or the `ImageBuildResponse` type altogether.

[moby@4a8b3ca]: 4a8b3cad60
[moby@535c4c9]: 535c4c9a59

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit af65ee4584)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 13:34:50 +02:00
f77defa891 cli/command/plugin: remove special error handling on install, upgrade
Similar to 323fbc485e - this code was added
in [moby@c127d96], but used string-matching to detect cases where a user
tried to install an image as plugin. However, this handling no longer matched
any error-strings, so no longer worked:

    docker plugin install busybox
    Error response from daemon: did not find plugin config for specified reference docker.io/library/busybox:latest

[moby@c127d96]: c127d9614f

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit fb3f2da50e)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 13:34:34 +02:00
4a677b86a6 cli/command/image: remove special handling for plugin errors on pull
This special handling was added in [moby@9b6dcc8], and later updated in
[moby@c127d96], but it fully depended on string-matching, which is brittle.
Testing the original ticket that lead to this handling, it looks like the
string matching no longer works, and the daemon error is returned as-is:

With graphdrivers:

    docker pull tiborvass/no-remove
    Using default tag: latest
    Error response from daemon: Encountered remote "application/vnd.docker.plugin.v0+json"(unknown) when fetching

With containerd snapshotters enabled:

    docker pull tiborvass/no-remove
    Using default tag: latest
    latest: Pulling from tiborvass/no-remove
    cf635291f7c9: Download complete
    failed to unpack image on snapshotter overlayfs: mismatched image rootfs and manifest layers

The error-message for containerd can probably be improved, but as the special
handling in the CLI no longer works, we can remove it.

[moby@9b6dcc8]: 9b6dcc8b9d
[moby@c127d96]: c127d9614f

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 323fbc485e)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 13:33:40 +02:00
1cf78c49ab cli/command/container: prevent panic during stats on empty event Actor.ID
This code was missing a check for the ID field before truncating it to a
shorter length for presentation. This would result in a panic if an event
would either have an empty ID field or a shorter length ID;

    panic: runtime error: slice bounds out of range [:12] with length 0

    goroutine 82 [running]:
    github.com/docker/cli/cli/command/container.RunStats.func2({{0x0, 0x0}, {0x0, 0x0}, {0x0, 0x0}, {0x40001fcba0, 0x9}, {0x40001fcba9, 0x5}, ...})
        /go/src/github.com/docker/cli/cli/command/container/stats.go:146 +0x1d0
    created by github.com/docker/cli/cli/command/container.(*eventHandler).watch in goroutine 6
        /go/src/github.com/docker/cli/cli/command/container/stats.go:363 +0x1c8

We need to look at this code in general; the truncated ID is passed to
NewStats, which uses the ID to propagate the `Container` field in the
`StatsEntry` struct. which is not used in the default format used by
`docker stats` and, having the same content as the `ID` field on the
same struct, doesn't make it very useful, other than being able to
present it under a `CONTAINER` column (instead of `CONTAINER ID`);
we should consider deprecating it; there may be some subtle things
to look into here; the `Container` field originally held the container
name. This was changed in [moby@ef915fd], which introduced separate
`ID` and `Name` fields, renaming the old `Name` field to container.

Looking at [`Stats.SetStatistics()`] and related code in [stats_helpers.go],
the `Container` field is used as the "canonical" reference for the stats
record; this allows the stats _data_ to be refreshed when a new stats
sample arrives for the same container (also see [moby@929a77b], which
moved locking to the `Stats` wrapper struct). This construct allows to
account for intermediate states, where a stats sample was incomplete
or could produce an error; in that case, the reference to the container
for which the stats were sampled is kept to allow removing a container
from the list once the container was removed. We should consider removing
`Container` as a formatting option, and moving the `Container` field to
the outer struct; this makes the outer struct responsible for keeping a
reference to the container, allowing the `StatsEntry` as a whole to be
replaced atomically.

This patch only addresses the panic;

- It changes the logic to preserve the container ID verbatim instead
  of truncating. This allows stats samples to be matched against the
  `Actor.ID` as-is.
- Truncating the `Container` is moved to the presentation logic;
  currently this does not take `--no-trunc` into account to keep
  the existing behavior, but we can (should) consider adding this.
- Logging is improved to use structured logs, and an extra check is
  added to prevent empty IDs from being added as watcher.

[`Stats.SetStatistics()`]: 82281087e3/cli/command/container/formatter_stats.go (L88-L94)
[moby@ef915fd]: ef915fd036
[moby@929a77b]: 929a77b814
[stats_helpers.go]: 82281087e3/cli/command/container/stats_helpers.go (L26-L51)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9b79e48646)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 13:30:08 +02:00
2fb1298416 cli/command/container: improve TestContainerStatsContext
- Use sub-tests
- Don't use un-named keys
- Add test-cases for 'Name', 'ID' and custom container names

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b9314938b7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 13:29:59 +02:00
a0ebf3e35c cli/command/container: improve TestContainerStatsContext
- Don't use unnamed keys
- Use sub-tests
- Add test-cases for Name and ID fields

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b8cda96d11)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 13:29:51 +02:00
428518a84a gha: update test-matrix: remove docker 23.x, 26.x, add 25.x
- Mirantis Container Runtime (MCR) 23.0 reached EOL, and the next LTS
  version of MCR is 25.x
- Docker 26.x reached EOL and is no longer maintained

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 83e40c39b4)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 13:26:32 +02:00
c1914f73f5 build(deps): bump actions/setup-go from 5 to 6
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 44e66a97a9)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 13:25:53 +02:00
cab5014d57 templates: deprecate NewParse()
It it just a chain of `New("sometag").Parse(...)`, and most of our
uses don't use a tag for the template, so can call Parse.

There's no public users of this function, but deprecating it first
just in case.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 7ab3e7e774)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-24 13:14:00 +02:00
7c3ec3ec02 Merge pull request #6428 from thaJeztah/28.x_backport_avoid_client_types_in_opts
[28.x backport] don't wrap client options
2025-09-04 15:46:08 -07:00
aa976192cb cli/command: don't wrap client options
We may still change this, but in the client module, the signature
of the client.Opt changed to now include a non-exported type, which
means that we can't construct a custom option that is implemented
using client options:

    #18 16.94 # github.com/docker/cli/cli/context/docker
    #18 16.94 cli/context/docker/load.go:105:29: cannot use withHTTPClient(tlsConfig) (value of type func(*client.Client) error) as client.Opt value in argument to append
    #18 16.94 cli/context/docker/load.go:152:6: cannot use c (variable of type *client.Client) as *client.clientConfig value in argument to client.WithHTTPClient(&http.Client{…})

We can consider exporting the `client.clientConfig` type (but keep its
fields non-exported), but for this use, we don't strictly need it, so
let's change the implementation to not having to depend on that.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit e7d14d905e)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-04 19:03:45 +02:00
2de1ea9769 cli/context/docker: don't wrap client options
We may still change this, but in the client module, the signature
of the client.Opt changed to now include a non-exported type, which
means that we can't construct a custom option that is implemented
using client options:

    #18 16.94 # github.com/docker/cli/cli/context/docker
    #18 16.94 cli/context/docker/load.go:105:29: cannot use withHTTPClient(tlsConfig) (value of type func(*client.Client) error) as client.Opt value in argument to append
    #18 16.94 cli/context/docker/load.go:152:6: cannot use c (variable of type *client.Client) as *client.clientConfig value in argument to client.WithHTTPClient(&http.Client{…})

We can consider exporting the `client.clientConfig` type (but keep its
fields non-exported), but for this use, we don't strictly need it, so
let's change the implementation to not having to depend on that.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b0b0e457f0)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-04 19:01:40 +02:00
0ac676b171 Merge pull request #6425 from vvoland/update-docker
[28.x] vendor: github.com/docker/docker v28.4.0
2025-09-04 01:01:14 +02:00
b627f18262 vendor: github.com/docker/docker v28.4.0
full diff: https://github.com/docker/docker/compare/249d679a6baf...v28.4.0

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-09-04 00:22:49 +02:00
d8eb465f86 Merge pull request #6424 from vvoland/update-docker
Some checks failed
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 23, local) (push) Has been cancelled
e2e / tests (alpine, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 26, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 23, local) (push) Has been cancelled
e2e / tests (debian, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 26, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-13) (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
[28.x] vendor: github.com/docker/docker v28.4.0-dev (249d679a6baf)
2025-09-03 22:52:20 +02:00
a83e3df40b vendor: github.com/docker/docker v28.4.0-dev (249d679a6baf)
full diff: https://github.com/docker/docker/compare/v28.4.0-rc.2...249d679a6baf

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-09-03 22:32:15 +02:00
ff5ea757b4 Merge pull request #6422 from vvoland/6421-28.x
[28.x backport] update to go1.24.7
2025-09-03 21:28:57 +02:00
c1d14aef3c Merge pull request #6423 from vvoland/update-docker
[28.x] vendor: github.com/docker/docker v28.4.0-rc.2
2025-09-03 21:04:36 +02:00
18adfd54a9 vendor: github.com/docker/docker v28.4.0-rc.2
full diff: https://github.com/docker/docker/compare/5d5332b00c76...v28.4.0-rc.2

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-09-03 20:58:26 +02:00
6e20b9d93c update to go1.24.7
This includes 1 security fix:

- net/http: CrossOriginProtection bypass patterns are over-broad

    When passing patterns to CrossOriginProtection.AddInsecureBypassPattern,
    requests that would have redirected to those patterns (e.g. without a trailing
    slash) were also exempted, which might be unexpected.

    Thanks to Marco Gazerro for reporting this issue.

    This is CVE-2025-47910 and Go issue https://go.dev/issue/75054.

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

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit f64b8a332d)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-09-03 20:44:05 +02:00
3cadbd82c5 Merge pull request #6420 from thaJeztah/28.x_backport_complete_pull
[28.x backport] add completion for docker image pull
2025-09-03 17:35:07 +02:00
b3df92053d add completion for docker image pull
With this patch, completion is provided for images already present
in the local image cache to help pulling the latest version of the
same tag;

    docker pull go<tab>
    golang:1.12    golang:1.18.0  golang:1.21    golang:1.24    gopher:latest
    golang:1.13    golang:1.20    golang:1.23    golang:latest

    docker pull golang:<tab>
    1.12    1.13    1.18.0  1.20    1.21    1.23    1.24    latest

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 5bf3c6793d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-03 15:42:07 +02:00
b89b069082 Merge pull request #6416 from vvoland/update-docker
Some checks failed
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 23, local) (push) Has been cancelled
e2e / tests (alpine, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 26, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 23, local) (push) Has been cancelled
e2e / tests (debian, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 26, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-13) (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
[28.x] vendor: github.com/docker/docker v28.4.0-rc.2-dev (5d5332b00c76)
2025-09-02 12:37:23 +02:00
9deb5e9cd3 vendor: github.com/docker/docker v28.4.0-rc.2-dev (5d5332b00c76)
full diff: https://github.com/docker/docker/compare/v28.4.0-rc.1...5d5332b00c76

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-09-02 12:15:13 +02:00
e8be20bc9c Merge pull request #6413 from thaJeztah/28.x_backport_deprecate_OauthLoginEscapeHatchEnvVar
[28.x backport] cli/command/registry: deprecate OauthLoginEscapeHatchEnvVar
2025-09-01 18:05:08 +02:00
f2cd1979c0 Merge pull request #6411 from thaJeztah/28.x_backport_deprecate_ReexecEnvvar
[28.x backport] cli-plugins/manager: deprecate ReexecEnvvar
2025-09-01 17:57:49 +02:00
991d942cc3 cli/command/registry: deprecate OauthLoginEscapeHatchEnvVar
This const was added in 846ecf59ff, but
only used internally. This patch deprecates the const, to be removed
in the next release.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 18cdc25bb4)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-01 17:47:37 +02:00
8e49313c0c cli-plugins/manager: deprecate ReexecEnvvar
This alias was added in 4321293972, which is
part of v28.0, but did not deprecate them. They are no longer used in the
CLI itself, but may be used by cli-plugin implementations.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 6fa7d18320)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-01 17:37:27 +02:00
6cd42da462 Merge pull request #6409 from thaJeztah/28.x_bump_engine
[28.x] vendor: github.com/docker/docker v28.4.0-rc.1
2025-09-01 13:14:46 +02:00
2b9489d827 [28.x] vendor: github.com/docker/docker v28.4.0-rc.1
full diff: https://github.com/moby/moby/compare/02b4a1a3decc...v28.4.0-rc.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-09-01 11:24:20 +02:00
fbeae8516b Merge pull request #6405 from thaJeztah/28.x_backport_deprecate_nocomplete
[28.x backport] cli/command/completion: deprecate NoComplete
2025-09-01 09:13:54 +02:00
d593e61275 Merge pull request #6403 from thaJeztah/28.x_backport_deprecate_context_funcs
[28.x backport] cli/command/context: deprecate exported types and functions
2025-09-01 09:12:27 +02:00
139968dff2 cli/command/completion: deprecate NoComplete
This function was an exact duplicate of [cobra.NoFileCompletions], so
deprecating it in favor of that.

[cobra.NoFileCompletions]: https://pkg.go.dev/github.com/spf13/cobra@v1.9.1#NoFileCompletions

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 2827d037ba)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-30 12:24:11 +02:00
925db59377 cli/command/context: deprecate exported types and functions
These functions and types are shallow wrappers around the context
store and were intended for internal use as implementation for the
CLI itself.

They were exported in 3126920af1 to be
used by plugins and Docker Desktop. However, there's currently no public
uses of this, and Docker Desktop does not use these functions.

This patch deprecates the exported functions as they were meant to be
implementation specific for the CLI. If there's a need to provide
utilities for manipulating the context-store other than through the
CLI itself, we can consider creating an SDK for that purpose.

This deprecates:

- `RunCreate` and `CreateOptions`
- `RunExport` and `ExportOptions`
- `RunImport`
- `RunRemove` and `RemoveOptions`
- `RunUpdate` and `UpdateOptions`
- `RunUse`

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 95eeafa551)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-08-30 12:13:27 +02:00
149 changed files with 1518 additions and 1009 deletions

View File

@ -88,7 +88,7 @@ jobs:
fi
-
name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: ${{ env.ARTIFACT_NAME }}
path: /tmp/out/*

View File

@ -61,19 +61,19 @@ jobs:
ln -s vendor.sum go.sum
-
name: Update Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "1.24.6"
go-version: "1.24.9"
-
name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: go
-
name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v4
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: "/language:go"

View File

@ -39,8 +39,7 @@ jobs:
engine-version:
- 28 # latest
- 27 # latest - 1
- 26 # github actions default
- 23 # mirantis lts
- 25 # mirantis lts
steps:
-
name: Checkout

View File

@ -53,8 +53,9 @@ jobs:
fail-fast: false
matrix:
os:
- macos-13 # macOS 13 on Intel
- macos-14 # macOS 14 on arm64 (Apple Silicon M1)
- macos-14 # macOS 14 on arm64 (Apple Silicon M1)
- macos-15-intel # macOS 15 on Intel
- macos-15 # macOS 15 on arm64 (Apple Silicon M1)
# - windows-2022 # FIXME: some tests are failing on the Windows runner, as well as on Appveyor since June 24, 2018: https://ci.appveyor.com/project/docker/cli/history
steps:
-
@ -64,9 +65,9 @@ jobs:
path: ${{ env.GOPATH }}/src/github.com/docker/cli
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "1.24.6"
go-version: "1.24.9"
-
name: Test
run: |

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.24.6"
go: "1.24.9"
timeout: 5m

View File

@ -8,13 +8,13 @@ ARG BASE_VARIANT=alpine
ARG ALPINE_VERSION=3.21
ARG BASE_DEBIAN_DISTRO=bookworm
ARG GO_VERSION=1.24.6
ARG GO_VERSION=1.24.9
ARG XX_VERSION=1.6.1
ARG GOVERSIONINFO_VERSION=v1.4.1
# GOTESTSUM_VERSION sets the version of gotestsum to install in the dev container.
# It must be a valid tag in the https://github.com/gotestyourself/gotestsum repository.
ARG GOTESTSUM_VERSION=v1.12.3
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

View File

@ -32,14 +32,12 @@ func (c *fakeCandidate) Metadata() ([]byte, error) {
func TestValidateCandidate(t *testing.T) {
const (
goodPluginName = metadata.NamePrefix + "goodplugin"
builtinName = metadata.NamePrefix + "builtin"
builtinAlias = metadata.NamePrefix + "alias"
builtinName = metadata.NamePrefix + "builtin"
builtinAlias = metadata.NamePrefix + "alias"
badPrefixPath = "/usr/local/libexec/cli-plugins/wobble"
badNamePath = "/usr/local/libexec/cli-plugins/docker-123456"
goodPluginPath = "/usr/local/libexec/cli-plugins/" + goodPluginName
metaExperimental = `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`
badPrefixPath = "/usr/local/libexec/cli-plugins/wobble"
badNamePath = "/usr/local/libexec/cli-plugins/docker-123456"
goodPluginPath = "/usr/local/libexec/cli-plugins/" + goodPluginName
)
fakeroot := &cobra.Command{Use: "docker"}
@ -51,31 +49,103 @@ func TestValidateCandidate(t *testing.T) {
})
for _, tc := range []struct {
name string
c *fakeCandidate
name string
plugin *fakeCandidate
// Either err or invalid may be non-empty, but not both (both can be empty for a good plugin).
err string
invalid string
expVer string
}{
/* Each failing one of the tests */
{name: "empty path", c: &fakeCandidate{path: ""}, err: "plugin candidate path cannot be empty"},
{name: "bad prefix", c: &fakeCandidate{path: badPrefixPath}, err: fmt.Sprintf("does not have %q prefix", metadata.NamePrefix)},
{name: "bad path", c: &fakeCandidate{path: badNamePath}, invalid: "did not match"},
{name: "builtin command", c: &fakeCandidate{path: builtinName}, invalid: `plugin "builtin" duplicates builtin command`},
{name: "builtin alias", c: &fakeCandidate{path: builtinAlias}, invalid: `plugin "alias" duplicates an alias of builtin command "builtin"`},
{name: "fetch failure", c: &fakeCandidate{path: goodPluginPath, exec: false}, invalid: fmt.Sprintf("failed to fetch metadata: faked a failure to exec %q", goodPluginPath)},
{name: "metadata not json", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `xyzzy`}, invalid: "invalid character"},
{name: "empty schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`}, invalid: `plugin SchemaVersion "" is not valid`},
{name: "invalid schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`}, invalid: `plugin SchemaVersion "xyzzy" is not valid`},
{name: "no vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`}, invalid: "plugin metadata does not define a vendor"},
{name: "empty vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`}, invalid: "plugin metadata does not define a vendor"},
// This one should work
{name: "valid", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`}},
{name: "experimental + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental}},
// Invalid cases.
{
name: "empty path",
plugin: &fakeCandidate{path: ""},
err: "plugin candidate path cannot be empty",
},
{
name: "bad prefix",
plugin: &fakeCandidate{path: badPrefixPath},
err: fmt.Sprintf("does not have %q prefix", metadata.NamePrefix),
},
{
name: "bad path",
plugin: &fakeCandidate{path: badNamePath},
invalid: "did not match",
},
{
name: "builtin command",
plugin: &fakeCandidate{path: builtinName},
invalid: `plugin "builtin" duplicates builtin command`,
},
{
name: "builtin alias",
plugin: &fakeCandidate{path: builtinAlias},
invalid: `plugin "alias" duplicates an alias of builtin command "builtin"`,
},
{
name: "fetch failure",
plugin: &fakeCandidate{path: goodPluginPath, exec: false},
invalid: fmt.Sprintf("failed to fetch metadata: faked a failure to exec %q", goodPluginPath),
},
{
name: "metadata not json",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `xyzzy`},
invalid: "invalid character",
},
{
name: "empty schemaversion",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`},
invalid: `plugin SchemaVersion version cannot be empty`,
},
{
name: "invalid schemaversion",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`},
invalid: `plugin SchemaVersion "xyzzy" has wrong format: must be <major>.<minor>.<patch>`,
},
{
name: "invalid schemaversion major",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "2.0.0"}`},
invalid: `plugin SchemaVersion "2.0.0" is not supported: must be lower than 2.0.0`,
},
{
name: "no vendor",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`},
invalid: "plugin metadata does not define a vendor",
},
{
name: "empty vendor",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`},
invalid: "plugin metadata does not define a vendor",
},
// Valid cases.
{
name: "valid",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`},
expVer: "0.1.0",
},
{
// Including the deprecated "experimental" field should not break processing.
name: "with legacy experimental",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`},
expVer: "0.1.0",
},
{
// note that this may not be supported by older CLIs
name: "new minor schema version",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.2.0", "Vendor": "e2e-testing"}`},
expVer: "0.2.0",
},
{
// note that this may not be supported by older CLIs
name: "new major schema version",
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "1.0.0", "Vendor": "e2e-testing"}`},
expVer: "1.0.0",
},
} {
t.Run(tc.name, func(t *testing.T) {
p, err := newPlugin(tc.c, fakeroot.Commands())
p, err := newPlugin(tc.plugin, fakeroot.Commands())
switch {
case tc.err != "":
assert.ErrorContains(t, err, tc.err)
@ -86,7 +156,7 @@ func TestValidateCandidate(t *testing.T) {
default:
assert.NilError(t, err)
assert.Equal(t, metadata.NamePrefix+p.Name, goodPluginName)
assert.Equal(t, p.SchemaVersion, "0.1.0")
assert.Equal(t, p.SchemaVersion, tc.expVer)
assert.Equal(t, p.Vendor, "e2e-testing")
}
})

View File

@ -38,6 +38,7 @@ 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,

View File

@ -2,6 +2,7 @@ package manager
import (
"context"
"errors"
"os"
"os/exec"
"path/filepath"
@ -13,6 +14,7 @@ import (
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/debug"
"github.com/fvbommel/sortorder"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
@ -23,6 +25,8 @@ const (
// used to originally invoke the docker CLI when executing a
// plugin. Assuming $PATH and $CWD remain unchanged this should allow
// the plugin to re-execute the original CLI.
//
// Deprecated: use [metadata.ReexecEnvvar]. This alias will be removed in the next release.
ReexecEnvvar = metadata.ReexecEnvvar
)
@ -72,9 +76,17 @@ func addPluginCandidatesFromDir(res map[string][]string, d string) {
return
}
for _, dentry := range dentries {
switch dentry.Type() & os.ModeType { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list
case 0, os.ModeSymlink:
// Regular file or symlink, keep going
switch mode := dentry.Type() & os.ModeType; mode { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list
case os.ModeSymlink:
if !debug.IsEnabled() {
// Skip broken symlinks unless debug is enabled. With debug
// enabled, this will print a warning in "docker info".
if _, err := os.Stat(filepath.Join(d, dentry.Name())); errors.Is(err, os.ErrNotExist) {
continue
}
}
case 0:
// Regular file, keep going
default:
// Something else, ignore.
continue

View File

@ -38,7 +38,7 @@ func TestListPluginCandidates(t *testing.T) {
"plugins3-target", // Will be referenced as a symlink from below
fs.WithFile("docker-plugin1", ""),
fs.WithDir("ignored3"),
fs.WithSymlink("docker-brokensymlink", "broken"), // A broken symlink is still a candidate (but would fail tests later)
fs.WithSymlink("docker-brokensymlink", "broken"), // A broken symlink is ignored
fs.WithFile("non-plugin-symlinked", ""), // This shouldn't appear, but ...
fs.WithSymlink("docker-symlinked", "non-plugin-symlinked"), // ... this link to it should.
),
@ -72,9 +72,6 @@ func TestListPluginCandidates(t *testing.T) {
"hardlink2": {
dir.Join("plugins2", "docker-hardlink2"),
},
"brokensymlink": {
dir.Join("plugins3", "docker-brokensymlink"),
},
"symlinked": {
dir.Join("plugins3", "docker-symlinked"),
},

View File

@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/docker/cli/cli-plugins/metadata"
@ -118,8 +119,8 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
p.Err = wrapAsPluginError(err, "invalid metadata")
return p, nil
}
if p.Metadata.SchemaVersion != "0.1.0" {
p.Err = newPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
if err := validateSchemaVersion(p.Metadata.SchemaVersion); err != nil {
p.Err = &pluginError{cause: err}
return p, nil
}
if p.Metadata.Vendor == "" {
@ -129,6 +130,31 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
return p, nil
}
// validateSchemaVersion validates if the plugin's schemaVersion is supported.
//
// The current schema-version is "0.1.0", but we don't want to break compatibility
// until v2.0.0 of the schema version. Check for the major version to be < 2.0.0.
//
// Note that CLI versions before 28.4.1 may not support these versions as they were
// hard-coded to only accept "0.1.0".
func validateSchemaVersion(version string) error {
if version == "0.1.0" {
return nil
}
if version == "" {
return errors.New("plugin SchemaVersion version cannot be empty")
}
major, _, ok := strings.Cut(version, ".")
majorVersion, err := strconv.Atoi(major)
if !ok || err != nil {
return fmt.Errorf("plugin SchemaVersion %q has wrong format: must be <major>.<minor>.<patch>", version)
}
if majorVersion > 1 {
return fmt.Errorf("plugin SchemaVersion %q is not supported: must be lower than 2.0.0", version)
}
return nil
}
// RunHook executes the plugin's hooks command
// and returns its unprocessed output.
func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte, error) {

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"`
}

View File

@ -80,19 +80,23 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadat
return cmd.Execute()
}
// Run is the top-level entry point to the CLI plugin framework. It should be called from your plugin's `main()` function.
func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) {
// Run is the top-level entry point to the CLI plugin framework. It should
// be called from the plugin's "main()" function. It initializes a new
// [command.DockerCli] instance with the given options before calling
// makeCmd to construct the plugin command, then invokes the plugin command
// using [RunPlugin].
func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata, ops ...command.CLIOption) {
otel.SetErrorHandler(debug.OTELErrorHandler)
dockerCli, err := command.NewDockerCli()
dockerCLI, err := command.NewDockerCli(ops...)
if err != nil {
fmt.Fprintln(os.Stderr, err)
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
plugin := makeCmd(dockerCli)
plugin := makeCmd(dockerCLI)
if err := RunPlugin(dockerCli, plugin, meta); err != nil {
if err := RunPlugin(dockerCLI, plugin, meta); err != nil {
var stErr cli.StatusError
if errors.As(err, &stErr) {
// StatusError should only be used for errors, and all errors should
@ -100,10 +104,10 @@ func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) {
if stErr.StatusCode == 0 { // FIXME(thaJeztah): this should never be used with a zero status-code. Check if we do this anywhere.
stErr.StatusCode = 1
}
_, _ = fmt.Fprintln(dockerCli.Err(), stErr)
_, _ = fmt.Fprintln(dockerCLI.Err(), stErr)
os.Exit(stErr.StatusCode)
}
_, _ = fmt.Fprintln(dockerCli.Err(), err)
_, _ = fmt.Fprintln(dockerCLI.Err(), err)
os.Exit(1)
}
}

View File

@ -376,13 +376,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)
}
}

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"}}

View File

@ -8,7 +8,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/internal/prompt"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/build"
@ -50,7 +49,7 @@ func newPruneCommand(dockerCLI command.Cli) *cobra.Command {
return nil
},
Annotations: map[string]string{"version": "1.39"},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()
@ -86,9 +85,12 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
}
report, err := dockerCli.Client().BuildCachePrune(ctx, build.CachePruneOptions{
All: options.all,
KeepStorage: options.keepStorage.Value(), // FIXME(thaJeztah): rewrite to use new options; see https://github.com/moby/moby/pull/48720
Filters: pruneFilters,
All: options.all,
// TODO(austinvazquez): remove when updated to use github.com/moby/moby/client@v0.1.0
// See https://github.com/moby/moby/pull/50772 for more details.
KeepStorage: options.keepStorage.Value(),
ReservedSpace: options.keepStorage.Value(),
Filters: pruneFilters,
})
if err != nil {
return 0, "", err

View File

@ -6,7 +6,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/checkpoint"
"github.com/spf13/cobra"
)
@ -30,7 +29,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
opts.checkpoint = args[1]
return runCreate(cmd.Context(), dockerCli, opts)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -48,9 +48,7 @@ type Cli interface {
Apply(ops ...CLIOption) error
config.Provider
ServerInfo() ServerInfo
DefaultVersion() string
CurrentVersion() string
ContentTrustEnabled() bool
BuildKitEnabled() (bool, error)
ContextStore() store.Store
CurrentContext() string
@ -78,6 +76,7 @@ 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
@ -89,6 +88,8 @@ type DockerCli struct {
}
// DefaultVersion returns [api.DefaultVersion].
//
// Deprecated: this function is no longer used and will be removed in the next release.
func (*DockerCli) DefaultVersion() string {
return api.DefaultVersion
}
@ -159,6 +160,8 @@ func (cli *DockerCli) ServerInfo() ServerInfo {
// ContentTrustEnabled returns whether content trust has been enabled by an
// environment variable.
//
// Deprecated: check the value of the DOCKER_CONTENT_TRUST environment variable to detect whether content-trust is enabled.
func (cli *DockerCli) ContentTrustEnabled() bool {
return cli.contentTrust
}
@ -269,7 +272,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
cli.contextStore = &ContextStoreWithDefault{
Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig),
Resolver: func() (*DefaultContext, error) {
return ResolveDefaultContext(cli.options, *cli.contextStoreConfig)
return resolveDefaultContext(cli.options, *cli.contextStoreConfig)
},
}
@ -306,17 +309,17 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.
contextStore := &ContextStoreWithDefault{
Store: store.New(config.ContextStoreDir(), storeConfig),
Resolver: func() (*DefaultContext, error) {
return ResolveDefaultContext(opts, storeConfig)
return resolveDefaultContext(opts, storeConfig)
},
}
endpoint, err := resolveDockerEndpoint(contextStore, resolveContextName(opts, configFile))
if err != nil {
return nil, errors.Wrap(err, "unable to resolve docker endpoint")
}
return newAPIClientFromEndpoint(endpoint, configFile)
return newAPIClientFromEndpoint(endpoint, configFile, client.WithUserAgent(UserAgent()))
}
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile) (client.APIClient, error) {
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile, extraOpts ...client.Opt) (client.APIClient, error) {
opts, err := ep.ClientOpts()
if err != nil {
return nil, err
@ -324,7 +327,14 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF
if len(configFile.HTTPHeaders) > 0 {
opts = append(opts, client.WithHTTPHeaders(configFile.HTTPHeaders))
}
opts = append(opts, withCustomHeadersFromEnv(), client.WithUserAgent(UserAgent()))
withCustomHeaders, err := withCustomHeadersFromEnv()
if err != nil {
return nil, err
}
if withCustomHeaders != nil {
opts = append(opts, withCustomHeaders)
}
opts = append(opts, extraOpts...)
return client.NewClientWithOpts(opts...)
}
@ -545,7 +555,8 @@ func (cli *DockerCli) initialize() error {
return
}
if cli.client == nil {
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile); cli.initErr != nil {
ops := []client.Opt{client.WithUserAgent(cli.userAgent)}
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil {
return
}
}
@ -558,6 +569,8 @@ func (cli *DockerCli) initialize() error {
}
// Apply all the operation on the cli
//
// Deprecated: this method is no longer used and will be removed in the next release if there are no remaining users.
func (cli *DockerCli) Apply(ops ...CLIOption) error {
for _, op := range ops {
if err := op(cli); err != nil {
@ -589,15 +602,18 @@ type ServerInfo struct {
// environment.
func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
defaultOps := []CLIOption{
WithContentTrustFromEnv(),
withContentTrustFromEnv(),
WithDefaultContextStoreConfig(),
WithStandardStreams(),
WithUserAgent(UserAgent()),
}
ops = append(defaultOps, ops...)
cli := &DockerCli{baseCtx: context.Background()}
if err := cli.Apply(ops...); err != nil {
return nil, err
for _, op := range ops {
if err := op(cli); err != nil {
return nil, err
}
}
return cli, nil
}
@ -613,7 +629,7 @@ func getServerHost(hosts []string, defaultToTLS bool) (string, error) {
}
}
// UserAgent returns the user agent string used for making API requests
// UserAgent returns the default user agent string used for making API requests.
func UserAgent() string {
return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")"
}

View File

@ -75,8 +75,8 @@ func WithErrorStream(err io.Writer) CLIOption {
}
}
// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
func WithContentTrustFromEnv() CLIOption {
// withContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
func withContentTrustFromEnv() CLIOption {
return func(cli *DockerCli) error {
cli.contentTrust = false
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
@ -89,7 +89,16 @@ func WithContentTrustFromEnv() CLIOption {
}
}
// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
//
// Deprecated: this option is no longer used, and will be removed in the next release.
func WithContentTrustFromEnv() CLIOption {
return withContentTrustFromEnv()
}
// WithContentTrust enables content trust on a cli.
//
// Deprecated: this option is no longer used, and will be removed in the next release.
func WithContentTrust(enabled bool) CLIOption {
return func(cli *DockerCli) error {
cli.contentTrust = enabled
@ -180,61 +189,70 @@ const envOverrideHTTPHeaders = "DOCKER_CUSTOM_HEADERS"
// override headers with the same name).
//
// TODO(thaJeztah): this is a client Option, and should be moved to the client. It is non-exported for that reason.
func withCustomHeadersFromEnv() client.Opt {
return func(apiClient *client.Client) error {
value := os.Getenv(envOverrideHTTPHeaders)
if value == "" {
return nil
}
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return invalidParameter(errors.Errorf(
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
envOverrideHTTPHeaders,
func withCustomHeadersFromEnv() (client.Opt, error) {
value := os.Getenv(envOverrideHTTPHeaders)
if value == "" {
return nil, nil
}
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return nil, invalidParameter(errors.Errorf(
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
envOverrideHTTPHeaders,
))
}
if len(fields) == 0 {
return nil, nil
}
env := map[string]string{}
for _, kv := range fields {
k, v, hasValue := strings.Cut(kv, "=")
// Only strip whitespace in keys; preserve whitespace in values.
k = strings.TrimSpace(k)
if k == "" {
return nil, invalidParameter(errors.Errorf(
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
envOverrideHTTPHeaders, kv,
))
}
if len(fields) == 0 {
return nil
// We don't currently allow empty key=value pairs, and produce an error.
// This is something we could allow in future (e.g. to read value
// from an environment variable with the same name). In the meantime,
// produce an error to prevent users from depending on this.
if !hasValue {
return nil, invalidParameter(errors.Errorf(
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
envOverrideHTTPHeaders, kv,
))
}
env := map[string]string{}
for _, kv := range fields {
k, v, hasValue := strings.Cut(kv, "=")
env[http.CanonicalHeaderKey(k)] = v
}
// Only strip whitespace in keys; preserve whitespace in values.
k = strings.TrimSpace(k)
if len(env) == 0 {
// We should probably not hit this case, as we don't skip values
// (only return errors), but we don't want to discard existing
// headers with an empty set.
return nil, nil
}
if k == "" {
return invalidParameter(errors.Errorf(
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
envOverrideHTTPHeaders, kv,
))
}
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
return client.WithHTTPHeaders(env), nil
}
// We don't currently allow empty key=value pairs, and produce an error.
// This is something we could allow in future (e.g. to read value
// from an environment variable with the same name). In the meantime,
// produce an error to prevent users from depending on this.
if !hasValue {
return invalidParameter(errors.Errorf(
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
envOverrideHTTPHeaders, kv,
))
}
env[http.CanonicalHeaderKey(k)] = v
// WithUserAgent configures the User-Agent string for cli HTTP requests.
func WithUserAgent(userAgent string) CLIOption {
return func(cli *DockerCli) error {
if userAgent == "" {
return errors.New("user agent cannot be blank")
}
if len(env) == 0 {
// We should probably not hit this case, as we don't skip values
// (only return errors), but we don't want to discard existing
// headers with an empty set.
return nil
}
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
return client.WithHTTPHeaders(env)(apiClient)
cli.userAgent = userAgent
return nil
}
}

View File

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

View File

@ -374,3 +374,26 @@ func TestSetGoDebug(t *testing.T) {
assert.Equal(t, "val1,val2=1", os.Getenv("GODEBUG"))
})
}
func TestNewDockerCliWithCustomUserAgent(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(
WithUserAgent("fake-agent/0.0.1"),
)
assert.NilError(t, err)
cli.currentContext = DefaultContextName
cli.options = opts
cli.configFile = &configfile.ConfigFile{}
_, err = cli.Client().Ping(context.Background())
assert.NilError(t, err)
assert.DeepEqual(t, received, "fake-agent/0.0.1")
}

View File

@ -141,7 +141,9 @@ func FileNames(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCom
return nil, cobra.ShellCompDirectiveDefault
}
// NoComplete is used for commands where there's no relevant completion
// NoComplete is used for commands where there's no relevant completion.
//
// Deprecated: use [cobra.NoFileCompletions]. This function will be removed in the next release.
func NoComplete(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveNoFileComp
}

View File

@ -288,12 +288,6 @@ func TestCompleteNetworkNames(t *testing.T) {
}
}
func TestCompleteNoComplete(t *testing.T) {
values, directives := NoComplete(nil, nil, "")
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp))
assert.Check(t, is.Len(values, 0))
}
func TestCompletePlatforms(t *testing.T) {
values, directives := Platforms(nil, nil, "")
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")

View File

@ -7,7 +7,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/swarm"
"github.com/moby/sys/sequential"
@ -47,7 +46,7 @@ func newConfigCreateCommand(dockerCLI command.Cli) *cobra.Command {
createOpts.file = args[1]
return runCreate(cmd.Context(), dockerCLI, createOpts)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()
flags.VarP(&createOpts.labels, "label", "l", "Config labels")

View File

@ -6,7 +6,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/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
@ -42,7 +41,7 @@ func newConfigListCommand(dockerCLI command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runList(cmd.Context(), dockerCLI, listOpts)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -96,7 +96,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
addPlatformFlag(flags, &options.platform)
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification")
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
copts = addFlags(flags)
addCompletions(cmd, dockerCLI)
@ -105,7 +105,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
})
return cmd

View File

@ -249,6 +249,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("DOCKER_CONTENT_TRUST", "true")
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
@ -258,7 +259,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
) (container.CreateResponse, error) {
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
},
}, test.EnableContentTrust)
})
fakeCLI.SetNotaryClient(tc.notaryFunc)
cmd := newCreateCommand(fakeCLI)
cmd.SetOut(io.Discard)

View File

@ -167,6 +167,7 @@ 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:]
}

View File

@ -5,45 +5,181 @@ import (
"testing"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/internal/test"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestContainerStatsContext(t *testing.T) {
containerID := test.RandomID()
const actorID = "c74518277ddc15a6afeaaeb06ee5f7433dcb27188224777c1efa7df1e8766d65"
var ctx statsContext
tt := []struct {
tests := []struct {
name string
stats StatsEntry
osType string
expValue string
expHeader string
call func() string
}{
{StatsEntry{Container: containerID}, "", containerID, containerHeader, ctx.Container},
{StatsEntry{CPUPercentage: 5.5}, "", "5.50%", cpuPercHeader, ctx.CPUPerc},
{StatsEntry{CPUPercentage: 5.5, IsInvalid: true}, "", "--", cpuPercHeader, ctx.CPUPerc},
{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3}, "", "0.31B / 12.3B", netIOHeader, ctx.NetIO},
{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, "", "--", netIOHeader, ctx.NetIO},
{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, "", "0.1B / 2.3B", blockIOHeader, ctx.BlockIO},
{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true}, "", "--", blockIOHeader, ctx.BlockIO},
{StatsEntry{MemoryPercentage: 10.2}, "", "10.20%", memPercHeader, ctx.MemPerc},
{StatsEntry{MemoryPercentage: 10.2, IsInvalid: true}, "", "--", memPercHeader, ctx.MemPerc},
{StatsEntry{MemoryPercentage: 10.2}, "windows", "--", memPercHeader, ctx.MemPerc},
{StatsEntry{Memory: 24, MemoryLimit: 30}, "", "24B / 30B", memUseHeader, ctx.MemUsage},
{StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, "", "-- / --", memUseHeader, ctx.MemUsage},
{StatsEntry{Memory: 24, MemoryLimit: 30}, "windows", "24B", winMemUseHeader, ctx.MemUsage},
{StatsEntry{PidsCurrent: 10}, "", "10", pidsHeader, ctx.PIDs},
{StatsEntry{PidsCurrent: 10, IsInvalid: true}, "", "--", pidsHeader, ctx.PIDs},
{StatsEntry{PidsCurrent: 10}, "windows", "--", pidsHeader, ctx.PIDs},
{
name: "Container id",
stats: StatsEntry{ID: actorID, Container: actorID},
expValue: actorID,
expHeader: containerHeader,
call: ctx.Container,
},
{
name: "Container name",
stats: StatsEntry{ID: actorID, Container: "a-long-container-name"},
expValue: "a-long-container-name",
expHeader: containerHeader,
call: ctx.Container,
},
{
name: "ID",
stats: StatsEntry{ID: actorID},
expValue: actorID,
expHeader: formatter.ContainerIDHeader,
call: ctx.ID,
},
{
name: "Name",
stats: StatsEntry{Name: "/container-name"},
expValue: "container-name",
expHeader: formatter.ContainerIDHeader,
call: ctx.Name,
},
{
name: "Name empty",
stats: StatsEntry{Name: ""},
expValue: "--",
expHeader: formatter.ContainerIDHeader,
call: ctx.Name,
},
{
name: "Name prefix only",
stats: StatsEntry{Name: "/"},
expValue: "--",
expHeader: formatter.ContainerIDHeader,
call: ctx.Name,
},
{
name: "CPUPerc",
stats: StatsEntry{CPUPercentage: 5.5},
expValue: "5.50%",
expHeader: cpuPercHeader,
call: ctx.CPUPerc,
},
{
name: "CPUPerc invalid",
stats: StatsEntry{CPUPercentage: 5.5, IsInvalid: true},
expValue: "--",
expHeader: cpuPercHeader,
call: ctx.CPUPerc,
},
{
name: "NetIO",
stats: StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3},
expValue: "0.31B / 12.3B",
expHeader: netIOHeader,
call: ctx.NetIO,
},
{
name: "NetIO invalid",
stats: StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true},
expValue: "--",
expHeader: netIOHeader,
call: ctx.NetIO,
},
{
name: "BlockIO",
stats: StatsEntry{BlockRead: 0.1, BlockWrite: 2.3},
expValue: "0.1B / 2.3B",
expHeader: blockIOHeader,
call: ctx.BlockIO,
},
{
name: "BlockIO invalid",
stats: StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true},
expValue: "--",
expHeader: blockIOHeader,
call: ctx.BlockIO,
},
{
name: "MemPerc",
stats: StatsEntry{MemoryPercentage: 10.2},
expValue: "10.20%",
expHeader: memPercHeader,
call: ctx.MemPerc,
},
{
name: "MemPerc invalid",
stats: StatsEntry{MemoryPercentage: 10.2, IsInvalid: true},
expValue: "--",
expHeader: memPercHeader,
call: ctx.MemPerc,
},
{
name: "MemPerc windows",
stats: StatsEntry{MemoryPercentage: 10.2},
osType: "windows",
expValue: "--",
expHeader: memPercHeader,
call: ctx.MemPerc,
},
{
name: "MemUsage",
stats: StatsEntry{Memory: 24, MemoryLimit: 30},
expValue: "24B / 30B",
expHeader: memUseHeader,
call: ctx.MemUsage,
},
{
name: "MemUsage invalid",
stats: StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true},
expValue: "-- / --",
expHeader: memUseHeader,
call: ctx.MemUsage,
},
{
name: "MemUsage windows",
stats: StatsEntry{Memory: 24, MemoryLimit: 30},
osType: "windows",
expValue: "24B",
expHeader: winMemUseHeader,
call: ctx.MemUsage,
},
{
name: "PIDs",
stats: StatsEntry{PidsCurrent: 10},
expValue: "10",
expHeader: pidsHeader,
call: ctx.PIDs,
},
{
name: "PIDs invalid",
stats: StatsEntry{PidsCurrent: 10, IsInvalid: true},
expValue: "--",
expHeader: pidsHeader,
call: ctx.PIDs,
},
{
name: "PIDs windows",
stats: StatsEntry{PidsCurrent: 10},
osType: "windows",
expValue: "--",
expHeader: pidsHeader,
call: ctx.PIDs,
},
}
for _, te := range tt {
ctx = statsContext{s: te.stats, os: te.osType}
if v := te.call(); v != te.expValue {
t.Fatalf("Expected %q, got %q", te.expValue, v)
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ctx = statsContext{s: tc.stats, os: tc.osType}
if v := tc.call(); v != tc.expValue {
t.Fatalf("Expected %q, got %q", tc.expValue, v)
}
})
}
}

View File

@ -6,7 +6,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/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
@ -50,7 +49,7 @@ func newPsCommand(dockerCLI command.Cli) *cobra.Command {
"category-top": "3",
"aliases": "docker container ls, docker container list, docker container ps, docker ps",
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()
@ -88,7 +87,7 @@ func buildContainerListOptions(options *psOptions) (*container.ListOptions, erro
// always validate template when `--format` is used, for consistency
if len(options.format) > 0 {
tmpl, err := templates.NewParse("", options.format)
tmpl, err := templates.Parse(options.format)
if err != nil {
return nil, errors.Wrap(err, "failed to parse template")
}

View File

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"net"
"os"
"path"
"path/filepath"
@ -350,7 +351,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
// Validate the input mac address
if copts.macAddress != "" {
if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil {
if _, err := net.ParseMAC(strings.TrimSpace(copts.macAddress)); err != nil {
return nil, errors.Errorf("%s is not a valid mac address", copts.macAddress)
}
}
@ -883,7 +884,7 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End
}
}
if ep.MacAddress != "" {
if _, err := opts.ValidateMACAddress(ep.MacAddress); err != nil {
if _, err := net.ParseMAC(strings.TrimSpace(ep.MacAddress)); err != nil {
return nil, errors.Errorf("%s is not a valid mac address", ep.MacAddress)
}
epConfig.MacAddress = ep.MacAddress

View File

@ -6,7 +6,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/internal/prompt"
"github.com/docker/cli/opts"
"github.com/docker/go-units"
@ -45,7 +44,7 @@ func newPruneCommand(dockerCLI command.Cli) *cobra.Command {
return nil
},
Annotations: map[string]string{"version": "1.25"},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -10,6 +10,7 @@ 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/docker/docker/api/types/container"
"github.com/moby/sys/signal"
@ -74,7 +75,7 @@ func newRunCommand(dockerCLI command.Cli) *cobra.Command {
// TODO(thaJeztah): consider adding platform as "image create option" on containerOptions
addPlatformFlag(flags, &options.platform)
flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification")
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
copts = addFlags(flags)
_ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys)
@ -84,7 +85,7 @@ func newRunCommand(dockerCLI command.Cli) *cobra.Command {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
})
return cmd

View File

@ -323,6 +323,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("DOCKER_CONTENT_TRUST", "true")
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
@ -332,7 +333,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
) (container.CreateResponse, error) {
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
},
}, test.EnableContentTrust)
})
fakeCLI.SetNotaryClient(tc.notaryFunc)
cmd := newRunCommand(fakeCLI)
cmd.SetArgs(tc.args)

View File

@ -139,7 +139,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
eh := newEventHandler()
if options.All {
eh.setHandler(events.ActionCreate, func(e events.Message) {
s := NewStats(e.Actor.ID[:12])
s := NewStats(e.Actor.ID)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
@ -148,7 +148,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
}
eh.setHandler(events.ActionStart, func(e events.Message) {
s := NewStats(e.Actor.ID[:12])
s := NewStats(e.Actor.ID)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
@ -157,7 +157,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
if !options.All {
eh.setHandler(events.ActionDie, func(e events.Message) {
cStats.remove(e.Actor.ID[:12])
cStats.remove(e.Actor.ID)
})
}
@ -210,7 +210,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
return err
}
for _, ctr := range cs {
s := NewStats(ctr.ID[:12])
s := NewStats(ctr.ID)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
@ -363,7 +363,12 @@ func (eh *eventHandler) watch(c <-chan events.Message) {
if !exists {
continue
}
logrus.Debugf("event handler: received event: %v", e)
if e.Actor.ID == "" {
logrus.WithField("event", e).Errorf("event handler: received %s event with empty ID", e.Action)
continue
}
logrus.WithField("event", e).Debugf("event handler: received %s event for: %s", e.Action, e.Actor.ID)
go h(e)
}
}

View File

@ -11,7 +11,6 @@ import (
"github.com/containerd/errdefs"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter/tabwriter"
"github.com/docker/cli/cli/context/docker"
"github.com/docker/cli/cli/context/store"
@ -19,6 +18,8 @@ import (
)
// CreateOptions are the options used for creating a context
//
// Deprecated: this type was for internal use and will be removed in the next release.
type CreateOptions struct {
Name string
Description string
@ -30,6 +31,18 @@ type CreateOptions struct {
metaData map[string]any
}
// createOptions are the options used for creating a context
type createOptions struct {
name string
description string
endpoint map[string]string
from string
// Additional Metadata to store in the context. This option is not
// currently exposed to the user.
metaData map[string]any
}
func longCreateDescription() string {
buf := bytes.NewBuffer(nil)
buf.WriteString("Create a context\n\nDocker endpoint config:\n\n")
@ -44,52 +57,68 @@ func longCreateDescription() string {
}
func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
opts := &CreateOptions{}
opts := createOptions{}
cmd := &cobra.Command{
Use: "create [OPTIONS] CONTEXT",
Short: "Create a context",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.Name = args[0]
return RunCreate(dockerCLI, opts)
opts.name = args[0]
return runCreate(dockerCLI, &opts)
},
Long: longCreateDescription(),
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()
flags.StringVar(&opts.Description, "description", "", "Description of the context")
flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint")
flags.StringVar(&opts.From, "from", "", "create context from a named context")
flags.StringVar(&opts.description, "description", "", "Description of the context")
flags.StringToStringVar(&opts.endpoint, "docker", nil, "set the docker endpoint")
flags.StringVar(&opts.from, "from", "", "create context from a named context")
return cmd
}
// RunCreate creates a Docker context
// Deprecated: this function was for internal use and will be removed in the next release.
func RunCreate(dockerCLI command.Cli, o *CreateOptions) error {
if o == nil {
o = &CreateOptions{}
}
return runCreate(dockerCLI, &createOptions{
name: o.Name,
description: o.Description,
endpoint: o.Docker,
metaData: o.metaData,
})
}
// runCreate creates a Docker context
func runCreate(dockerCLI command.Cli, opts *createOptions) error {
s := dockerCLI.ContextStore()
err := checkContextNameForCreation(s, o.Name)
err := checkContextNameForCreation(s, opts.name)
if err != nil {
return err
}
switch {
case o.From == "" && o.Docker == nil:
err = createFromExistingContext(s, dockerCLI.CurrentContext(), o)
case o.From != "":
err = createFromExistingContext(s, o.From, o)
case opts.from == "" && opts.endpoint == nil:
err = createFromExistingContext(s, dockerCLI.CurrentContext(), opts)
case opts.from != "":
err = createFromExistingContext(s, opts.from, opts)
default:
err = createNewContext(s, o)
err = createNewContext(s, opts)
}
if err == nil {
_, _ = fmt.Fprintln(dockerCLI.Out(), o.Name)
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully created context %q\n", o.Name)
_, _ = fmt.Fprintln(dockerCLI.Out(), opts.name)
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully created context %q\n", opts.name)
}
return err
}
func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
if o.Docker == nil {
func createNewContext(contextStore store.ReaderWriter, opts *createOptions) error {
if opts.endpoint == nil {
return errors.New("docker endpoint configuration is required")
}
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(contextStore, o.Docker)
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(contextStore, opts.endpoint)
if err != nil {
return fmt.Errorf("unable to create docker endpoint config: %w", err)
}
@ -98,10 +127,10 @@ func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
docker.DockerEndpoint: dockerEP,
},
Metadata: command.DockerContext{
Description: o.Description,
AdditionalFields: o.metaData,
Description: opts.description,
AdditionalFields: opts.metaData,
},
Name: o.Name,
Name: opts.name,
}
contextTLSData := store.ContextTLSData{}
if dockerTLS != nil {
@ -115,7 +144,7 @@ func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
if err := contextStore.CreateOrUpdate(contextMetadata); err != nil {
return err
}
return contextStore.ResetTLSMaterial(o.Name, &contextTLSData)
return contextStore.ResetTLSMaterial(opts.name, &contextTLSData)
}
func checkContextNameForCreation(s store.Reader, name string) error {
@ -131,16 +160,16 @@ func checkContextNameForCreation(s store.Reader, name string) error {
return nil
}
func createFromExistingContext(s store.ReaderWriter, fromContextName string, o *CreateOptions) error {
if len(o.Docker) != 0 {
func createFromExistingContext(s store.ReaderWriter, fromContextName string, opts *createOptions) error {
if len(opts.endpoint) != 0 {
return errors.New("cannot use --docker flag when --from is set")
}
reader := store.Export(fromContextName, &descriptionDecorator{
Reader: s,
description: o.Description,
description: opts.description,
})
defer reader.Close()
return store.Import(o.Name, s, reader)
return store.Import(opts.name, s, reader)
}
type descriptionDecorator struct {

View File

@ -60,7 +60,7 @@ func TestCreate(t *testing.T) {
assert.NilError(t, cli.ContextStore().CreateOrUpdate(store.Metadata{Name: "existing-context"}))
tests := []struct {
doc string
options CreateOptions
options createOptions
expecterErr string
}{
{
@ -69,30 +69,30 @@ func TestCreate(t *testing.T) {
},
{
doc: "reserved name",
options: CreateOptions{
Name: "default",
options: createOptions{
name: "default",
},
expecterErr: `"default" is a reserved context name`,
},
{
doc: "whitespace-only name",
options: CreateOptions{
Name: " ",
options: createOptions{
name: " ",
},
expecterErr: `context name " " is invalid`,
},
{
doc: "existing context",
options: CreateOptions{
Name: "existing-context",
options: createOptions{
name: "existing-context",
},
expecterErr: `context "existing-context" already exists`,
},
{
doc: "invalid docker host",
options: CreateOptions{
Name: "invalid-docker-host",
Docker: map[string]string{
options: createOptions{
name: "invalid-docker-host",
endpoint: map[string]string{
"host": "some///invalid/host",
},
},
@ -100,27 +100,27 @@ func TestCreate(t *testing.T) {
},
{
doc: "ssh host with skip-tls-verify=false",
options: CreateOptions{
Name: "skip-tls-verify-false",
Docker: map[string]string{
options: createOptions{
name: "skip-tls-verify-false",
endpoint: map[string]string{
"host": "ssh://example.com,skip-tls-verify=false",
},
},
},
{
doc: "ssh host with skip-tls-verify=true",
options: CreateOptions{
Name: "skip-tls-verify-true",
Docker: map[string]string{
options: createOptions{
name: "skip-tls-verify-true",
endpoint: map[string]string{
"host": "ssh://example.com,skip-tls-verify=true",
},
},
},
{
doc: "ssh host with skip-tls-verify=INVALID",
options: CreateOptions{
Name: "skip-tls-verify-invalid",
Docker: map[string]string{
options: createOptions{
name: "skip-tls-verify-invalid",
endpoint: map[string]string{
"host": "ssh://example.com",
"skip-tls-verify": "INVALID",
},
@ -129,9 +129,9 @@ func TestCreate(t *testing.T) {
},
{
doc: "unknown option",
options: CreateOptions{
Name: "unknown-option",
Docker: map[string]string{
options: createOptions{
name: "unknown-option",
endpoint: map[string]string{
"UNKNOWN": "value",
},
},
@ -140,7 +140,7 @@ func TestCreate(t *testing.T) {
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
err := RunCreate(cli, &tc.options)
err := runCreate(cli, &tc.options)
if tc.expecterErr == "" {
assert.NilError(t, err)
} else {
@ -159,9 +159,9 @@ func assertContextCreateLogging(t *testing.T, cli *test.FakeCli, n string) {
func TestCreateOrchestratorEmpty(t *testing.T) {
cli := makeFakeCli(t)
err := RunCreate(cli, &CreateOptions{
Name: "test",
Docker: map[string]string{},
err := runCreate(cli, &createOptions{
name: "test",
endpoint: map[string]string{},
})
assert.NilError(t, err)
assertContextCreateLogging(t, cli, "test")
@ -187,20 +187,20 @@ func TestCreateFromContext(t *testing.T) {
cli := makeFakeCli(t)
cli.ResetOutputBuffers()
assert.NilError(t, RunCreate(cli, &CreateOptions{
Name: "original",
Description: "original description",
Docker: map[string]string{
assert.NilError(t, runCreate(cli, &createOptions{
name: "original",
description: "original description",
endpoint: map[string]string{
keyHost: "tcp://42.42.42.42:2375",
},
}))
assertContextCreateLogging(t, cli, "original")
cli.ResetOutputBuffers()
assert.NilError(t, RunCreate(cli, &CreateOptions{
Name: "dummy",
Description: "dummy description",
Docker: map[string]string{
assert.NilError(t, runCreate(cli, &createOptions{
name: "dummy",
description: "dummy description",
endpoint: map[string]string{
keyHost: "tcp://24.24.24.24:2375",
},
}))
@ -211,11 +211,11 @@ func TestCreateFromContext(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cli.ResetOutputBuffers()
err := RunCreate(cli, &CreateOptions{
From: "original",
Name: tc.name,
Description: tc.description,
Docker: tc.docker,
err := runCreate(cli, &createOptions{
from: "original",
name: tc.name,
description: tc.description,
endpoint: tc.docker,
})
assert.NilError(t, err)
assertContextCreateLogging(t, cli, tc.name)
@ -251,10 +251,10 @@ func TestCreateFromCurrent(t *testing.T) {
cli := makeFakeCli(t)
cli.ResetOutputBuffers()
assert.NilError(t, RunCreate(cli, &CreateOptions{
Name: "original",
Description: "original description",
Docker: map[string]string{
assert.NilError(t, runCreate(cli, &createOptions{
name: "original",
description: "original description",
endpoint: map[string]string{
keyHost: "tcp://42.42.42.42:2375",
},
}))
@ -265,9 +265,9 @@ func TestCreateFromCurrent(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cli.ResetOutputBuffers()
err := RunCreate(cli, &CreateOptions{
Name: tc.name,
Description: tc.description,
err := runCreate(cli, &createOptions{
name: tc.name,
description: tc.description,
})
assert.NilError(t, err)
assertContextCreateLogging(t, cli, tc.name)

View File

@ -21,14 +21,11 @@ func TestExportImportWithFile(t *testing.T) {
"MyCustomMetadata": t.Name(),
})
cli.ErrBuffer().Reset()
assert.NilError(t, RunExport(cli, &ExportOptions{
ContextName: "test",
Dest: contextFile,
}))
assert.NilError(t, runExport(cli, "test", contextFile))
assert.Equal(t, cli.ErrBuffer().String(), fmt.Sprintf("Written file %q\n", contextFile))
cli.OutBuffer().Reset()
cli.ErrBuffer().Reset()
assert.NilError(t, RunImport(cli, "test2", contextFile))
assert.NilError(t, runImport(cli, "test2", contextFile))
context1, err := cli.ContextStore().GetMetadata("test")
assert.NilError(t, err)
context2, err := cli.ContextStore().GetMetadata("test2")
@ -55,15 +52,12 @@ func TestExportImportPipe(t *testing.T) {
})
cli.ErrBuffer().Reset()
cli.OutBuffer().Reset()
assert.NilError(t, RunExport(cli, &ExportOptions{
ContextName: "test",
Dest: "-",
}))
assert.NilError(t, runExport(cli, "test", "-"))
assert.Equal(t, cli.ErrBuffer().String(), "")
cli.SetIn(streams.NewIn(io.NopCloser(bytes.NewBuffer(cli.OutBuffer().Bytes()))))
cli.OutBuffer().Reset()
cli.ErrBuffer().Reset()
assert.NilError(t, RunImport(cli, "test2", "-"))
assert.NilError(t, runImport(cli, "test2", "-"))
context1, err := cli.ContextStore().GetMetadata("test")
assert.NilError(t, err)
context2, err := cli.ContextStore().GetMetadata("test2")
@ -88,6 +82,6 @@ func TestExportExistingFile(t *testing.T) {
cli := makeFakeCli(t)
cli.ErrBuffer().Reset()
assert.NilError(t, os.WriteFile(contextFile, []byte{}, 0o644))
err := RunExport(cli, &ExportOptions{ContextName: "test", Dest: contextFile})
err := runExport(cli, "test", contextFile)
assert.Assert(t, os.IsExist(err))
}

View File

@ -13,6 +13,8 @@ import (
)
// ExportOptions are the options used for exporting a context
//
// Deprecated: this type was for internal use and will be removed in the next release.
type ExportOptions struct {
ContextName string
Dest string
@ -24,15 +26,14 @@ func newExportCommand(dockerCLI command.Cli) *cobra.Command {
Short: "Export a context to a tar archive FILE or a tar stream on STDOUT.",
Args: cli.RequiresRangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
opts := &ExportOptions{
ContextName: args[0],
}
contextName := args[0]
var dest string
if len(args) == 2 {
opts.Dest = args[1]
dest = args[1]
} else {
opts.Dest = opts.ContextName + ".dockercontext"
dest = contextName + ".dockercontext"
}
return RunExport(dockerCLI, opts)
return runExport(dockerCLI, contextName, dest)
},
ValidArgsFunction: completeContextNames(dockerCLI, 1, true),
}
@ -65,11 +66,21 @@ func writeTo(dockerCli command.Cli, reader io.Reader, dest string) error {
}
// RunExport exports a Docker context
//
// Deprecated: this function was for internal use and will be removed in the next release.
func RunExport(dockerCli command.Cli, opts *ExportOptions) error {
if err := store.ValidateContextName(opts.ContextName); err != nil && opts.ContextName != command.DefaultContextName {
if opts == nil {
opts = &ExportOptions{}
}
return runExport(dockerCli, opts.ContextName, opts.Dest)
}
// runExport exports a Docker context.
func runExport(dockerCLI command.Cli, contextName string, dest string) error {
if err := store.ValidateContextName(contextName); err != nil && contextName != command.DefaultContextName {
return err
}
reader := store.Export(opts.ContextName, dockerCli.ContextStore())
reader := store.Export(contextName, dockerCLI.ContextStore())
defer reader.Close()
return writeTo(dockerCli, reader, opts.Dest)
return writeTo(dockerCLI, reader, dest)
}

View File

@ -18,7 +18,7 @@ func newImportCommand(dockerCli command.Cli) *cobra.Command {
Short: "Import a context from a tar or zip file",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return RunImport(dockerCli, args[0], args[1])
return runImport(dockerCli, args[0], args[1])
},
// TODO(thaJeztah): this should also include "-"
ValidArgsFunction: completion.FileNames,
@ -27,14 +27,21 @@ func newImportCommand(dockerCli command.Cli) *cobra.Command {
}
// RunImport imports a Docker context
func RunImport(dockerCli command.Cli, name string, source string) error {
if err := checkContextNameForCreation(dockerCli.ContextStore(), name); err != nil {
//
// Deprecated: this function was for internal use and will be removed in the next release.
func RunImport(dockerCLI command.Cli, name string, source string) error {
return runImport(dockerCLI, name, source)
}
// runImport imports a Docker context.
func runImport(dockerCLI command.Cli, name string, source string) error {
if err := checkContextNameForCreation(dockerCLI.ContextStore(), name); err != nil {
return err
}
var reader io.Reader
if source == "-" {
reader = dockerCli.In()
reader = dockerCLI.In()
} else {
f, err := os.Open(source)
if err != nil {
@ -44,11 +51,11 @@ func RunImport(dockerCli command.Cli, name string, source string) error {
reader = f
}
if err := store.Import(name, dockerCli.ContextStore(), reader); err != nil {
if err := store.Import(name, dockerCLI.ContextStore(), reader); err != nil {
return err
}
_, _ = fmt.Fprintln(dockerCli.Out(), name)
_, _ = fmt.Fprintf(dockerCli.Err(), "Successfully imported context %q\n", name)
_, _ = fmt.Fprintln(dockerCLI.Out(), name)
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully imported context %q\n", name)
return nil
}

View File

@ -10,7 +10,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/command/formatter"
"github.com/docker/cli/cli/context/docker"
flagsHelper "github.com/docker/cli/cli/flags"
@ -34,7 +33,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runList(dockerCli, opts)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -19,10 +19,10 @@ func createTestContexts(t *testing.T, cli command.Cli, name ...string) {
func createTestContext(t *testing.T, cli command.Cli, name string, metaData map[string]any) {
t.Helper()
err := RunCreate(cli, &CreateOptions{
Name: name,
Description: "description of " + name,
Docker: map[string]string{keyHost: "https://someswarmserver.example.com"},
err := runCreate(cli, &createOptions{
name: name,
description: "description of " + name,
endpoint: map[string]string{keyHost: "https://someswarmserver.example.com"},
metaData: metaData,
})

View File

@ -11,34 +11,48 @@ import (
)
// RemoveOptions are the options used to remove contexts
//
// Deprecated: this type was for internal use and will be removed in the next release.
type RemoveOptions struct {
Force bool
}
// removeOptions are the options used to remove contexts.
type removeOptions struct {
force bool
}
func newRemoveCommand(dockerCLI command.Cli) *cobra.Command {
var opts RemoveOptions
var opts removeOptions
cmd := &cobra.Command{
Use: "rm CONTEXT [CONTEXT...]",
Aliases: []string{"remove"},
Short: "Remove one or more contexts",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return RunRemove(dockerCLI, opts, args)
return runRemove(dockerCLI, opts, args)
},
ValidArgsFunction: completeContextNames(dockerCLI, -1, false),
}
cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Force the removal of a context in use")
cmd.Flags().BoolVarP(&opts.force, "force", "f", false, "Force the removal of a context in use")
return cmd
}
// RunRemove removes one or more contexts
func RunRemove(dockerCLI command.Cli, opts RemoveOptions, names []string) error {
//
// Deprecated: this function was for internal use and will be removed in the next release.
func RunRemove(dockerCLI command.Cli, opts removeOptions, names []string) error {
return runRemove(dockerCLI, opts, names)
}
// runRemove removes one or more contexts.
func runRemove(dockerCLI command.Cli, opts removeOptions, names []string) error {
var errs []error
currentCtx := dockerCLI.CurrentContext()
for _, name := range names {
if name == "default" {
errs = append(errs, errors.New(`context "default" cannot be removed`))
} else if err := doRemove(dockerCLI, name, name == currentCtx, opts.Force); err != nil {
} else if err := doRemove(dockerCLI, name, name == currentCtx, opts.force); err != nil {
errs = append(errs, err)
} else {
_, _ = fmt.Fprintln(dockerCLI.Out(), name)

View File

@ -14,7 +14,7 @@ import (
func TestRemove(t *testing.T) {
cli := makeFakeCli(t)
createTestContexts(t, cli, "current", "other")
assert.NilError(t, RunRemove(cli, RemoveOptions{}, []string{"other"}))
assert.NilError(t, runRemove(cli, removeOptions{}, []string{"other"}))
_, err := cli.ContextStore().GetMetadata("current")
assert.NilError(t, err)
_, err = cli.ContextStore().GetMetadata("other")
@ -24,10 +24,10 @@ func TestRemove(t *testing.T) {
func TestRemoveNotAContext(t *testing.T) {
cli := makeFakeCli(t)
createTestContexts(t, cli, "current", "other")
err := RunRemove(cli, RemoveOptions{}, []string{"not-a-context"})
err := runRemove(cli, removeOptions{}, []string{"not-a-context"})
assert.ErrorContains(t, err, `context "not-a-context" does not exist`)
err = RunRemove(cli, RemoveOptions{Force: true}, []string{"not-a-context"})
err = runRemove(cli, removeOptions{force: true}, []string{"not-a-context"})
assert.NilError(t, err)
}
@ -35,7 +35,7 @@ func TestRemoveCurrent(t *testing.T) {
cli := makeFakeCli(t)
createTestContexts(t, cli, "current", "other")
cli.SetCurrentContext("current")
err := RunRemove(cli, RemoveOptions{}, []string{"current"})
err := runRemove(cli, removeOptions{}, []string{"current"})
assert.ErrorContains(t, err, `context "current" is in use, set -f flag to force remove`)
}
@ -49,7 +49,7 @@ func TestRemoveCurrentForce(t *testing.T) {
cli := makeFakeCli(t, withCliConfig(testCfg))
createTestContexts(t, cli, "current", "other")
cli.SetCurrentContext("current")
assert.NilError(t, RunRemove(cli, RemoveOptions{Force: true}, []string{"current"}))
assert.NilError(t, runRemove(cli, removeOptions{force: true}, []string{"current"}))
reloadedConfig, err := config.Load(configDir)
assert.NilError(t, err)
assert.Equal(t, "", reloadedConfig.CurrentContext)
@ -59,6 +59,6 @@ func TestRemoveDefault(t *testing.T) {
cli := makeFakeCli(t)
createTestContext(t, cli, "other", nil)
cli.SetCurrentContext("current")
err := RunRemove(cli, RemoveOptions{}, []string{"default"})
err := runRemove(cli, removeOptions{}, []string{"default"})
assert.ErrorContains(t, err, `context "default" cannot be removed`)
}

View File

@ -5,7 +5,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/spf13/cobra"
)
@ -19,7 +18,7 @@ func newShowCommand(dockerCli command.Cli) *cobra.Command {
runShow(dockerCli)
return nil
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
return cmd
}

View File

@ -13,12 +13,21 @@ import (
)
// UpdateOptions are the options used to update a context
//
// Deprecated: this type was for internal use and will be removed in the next release.
type UpdateOptions struct {
Name string
Description string
Docker map[string]string
}
// updateOptions are the options used to update a context.
type updateOptions struct {
name string
description string
endpoint map[string]string
}
func longUpdateDescription() string {
buf := bytes.NewBuffer(nil)
buf.WriteString("Update a context\n\nDocker endpoint config:\n\n")
@ -33,31 +42,45 @@ func longUpdateDescription() string {
}
func newUpdateCommand(dockerCLI command.Cli) *cobra.Command {
opts := &UpdateOptions{}
opts := updateOptions{}
cmd := &cobra.Command{
Use: "update [OPTIONS] CONTEXT",
Short: "Update a context",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.Name = args[0]
return RunUpdate(dockerCLI, opts)
opts.name = args[0]
return runUpdate(dockerCLI, &opts)
},
Long: longUpdateDescription(),
ValidArgsFunction: completeContextNames(dockerCLI, 1, false),
}
flags := cmd.Flags()
flags.StringVar(&opts.Description, "description", "", "Description of the context")
flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint")
flags.StringVar(&opts.description, "description", "", "Description of the context")
flags.StringToStringVar(&opts.endpoint, "docker", nil, "set the docker endpoint")
return cmd
}
// RunUpdate updates a Docker context
//
// Deprecated: this function was for internal use and will be removed in the next release.
func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error {
if err := store.ValidateContextName(o.Name); err != nil {
if o == nil {
o = &UpdateOptions{}
}
return runUpdate(dockerCLI, &updateOptions{
name: o.Name,
description: o.Description,
endpoint: o.Docker,
})
}
// runUpdate updates a Docker context.
func runUpdate(dockerCLI command.Cli, opts *updateOptions) error {
if err := store.ValidateContextName(opts.name); err != nil {
return err
}
s := dockerCLI.ContextStore()
c, err := s.GetMetadata(o.Name)
c, err := s.GetMetadata(opts.name)
if err != nil {
return err
}
@ -65,16 +88,16 @@ func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error {
if err != nil {
return err
}
if o.Description != "" {
dockerContext.Description = o.Description
if opts.description != "" {
dockerContext.Description = opts.description
}
c.Metadata = dockerContext
tlsDataToReset := make(map[string]*store.EndpointTLSData)
if o.Docker != nil {
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(s, o.Docker)
if opts.endpoint != nil {
dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(s, opts.endpoint)
if err != nil {
return fmt.Errorf("unable to create docker endpoint config: %w", err)
}
@ -88,13 +111,13 @@ func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error {
return err
}
for ep, tlsData := range tlsDataToReset {
if err := s.ResetEndpointTLSMaterial(o.Name, ep, tlsData); err != nil {
if err := s.ResetEndpointTLSMaterial(opts.name, ep, tlsData); err != nil {
return err
}
}
_, _ = fmt.Fprintln(dockerCLI.Out(), o.Name)
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully updated context %q\n", o.Name)
_, _ = fmt.Fprintln(dockerCLI.Out(), opts.name)
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully updated context %q\n", opts.name)
return nil
}

View File

@ -11,16 +11,16 @@ import (
func TestUpdateDescriptionOnly(t *testing.T) {
cli := makeFakeCli(t)
err := RunCreate(cli, &CreateOptions{
Name: "test",
Docker: map[string]string{},
err := runCreate(cli, &createOptions{
name: "test",
endpoint: map[string]string{},
})
assert.NilError(t, err)
cli.OutBuffer().Reset()
cli.ErrBuffer().Reset()
assert.NilError(t, RunUpdate(cli, &UpdateOptions{
Name: "test",
Description: "description",
assert.NilError(t, runUpdate(cli, &updateOptions{
name: "test",
description: "description",
}))
c, err := cli.ContextStore().GetMetadata("test")
assert.NilError(t, err)
@ -35,9 +35,9 @@ func TestUpdateDescriptionOnly(t *testing.T) {
func TestUpdateDockerOnly(t *testing.T) {
cli := makeFakeCli(t)
createTestContext(t, cli, "test", nil)
assert.NilError(t, RunUpdate(cli, &UpdateOptions{
Name: "test",
Docker: map[string]string{
assert.NilError(t, runUpdate(cli, &updateOptions{
name: "test",
endpoint: map[string]string{
keyHost: "tcp://some-host",
},
}))
@ -52,14 +52,14 @@ func TestUpdateDockerOnly(t *testing.T) {
func TestUpdateInvalidDockerHost(t *testing.T) {
cli := makeFakeCli(t)
err := RunCreate(cli, &CreateOptions{
Name: "test",
Docker: map[string]string{},
err := runCreate(cli, &createOptions{
name: "test",
endpoint: map[string]string{},
})
assert.NilError(t, err)
err = RunUpdate(cli, &UpdateOptions{
Name: "test",
Docker: map[string]string{
err = runUpdate(cli, &updateOptions{
name: "test",
endpoint: map[string]string{
keyHost: "some///invalid/host",
},
})

View File

@ -17,7 +17,7 @@ func newUseCommand(dockerCLI command.Cli) *cobra.Command {
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
return RunUse(dockerCLI, name)
return runUse(dockerCLI, name)
},
ValidArgsFunction: completeContextNames(dockerCLI, 1, false),
}
@ -25,7 +25,14 @@ func newUseCommand(dockerCLI command.Cli) *cobra.Command {
}
// RunUse set the current Docker context
//
// Deprecated: this function was for internal use and will be removed in the next release.
func RunUse(dockerCLI command.Cli, name string) error {
return runUse(dockerCLI, name)
}
// runUse set the current Docker context
func runUse(dockerCLI command.Cli, name string) error {
// configValue uses an empty string for "default"
var configValue string
if name != command.DefaultContextName {

View File

@ -23,9 +23,9 @@ func TestUse(t *testing.T) {
configFilePath := filepath.Join(configDir, "config.json")
testCfg := configfile.New(configFilePath)
cli := makeFakeCli(t, withCliConfig(testCfg))
err := RunCreate(cli, &CreateOptions{
Name: "test",
Docker: map[string]string{},
err := runCreate(cli, &createOptions{
name: "test",
endpoint: map[string]string{},
})
assert.NilError(t, err)
assert.NilError(t, newUseCommand(cli).RunE(nil, []string{"test"}))
@ -89,9 +89,9 @@ func TestUseHostOverride(t *testing.T) {
configFilePath := filepath.Join(configDir, "config.json")
testCfg := configfile.New(configFilePath)
cli := makeFakeCli(t, withCliConfig(testCfg))
err := RunCreate(cli, &CreateOptions{
Name: "test",
Docker: map[string]string{},
err := runCreate(cli, &createOptions{
name: "test",
endpoint: map[string]string{},
})
assert.NilError(t, err)
@ -136,9 +136,9 @@ func TestUseHostOverrideEmpty(t *testing.T) {
assert.NilError(t, cli.Initialize(flags.NewClientOptions()))
}
loadCli()
err := RunCreate(cli, &CreateOptions{
Name: "test",
Docker: map[string]string{"host": socketPath},
err := runCreate(cli, &createOptions{
name: "test",
endpoint: map[string]string{"host": socketPath},
})
assert.NilError(t, err)

View File

@ -52,7 +52,14 @@ type EndpointDefaultResolver interface {
}
// ResolveDefaultContext creates a Metadata for the current CLI invocation parameters
//
// Deprecated: this function is exported for testing and meant for internal use. It will be removed in the next release.
func ResolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*DefaultContext, error) {
return resolveDefaultContext(opts, config)
}
// resolveDefaultContext creates a Metadata for the current CLI invocation parameters
func resolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*DefaultContext, error) {
contextTLSData := store.ContextTLSData{
Endpoints: make(map[string]store.EndpointTLSData),
}

View File

@ -59,7 +59,7 @@ func TestDefaultContextInitializer(t *testing.T) {
assert.NilError(t, err)
t.Setenv("DOCKER_HOST", "ssh://someswarmserver")
cli.configFile = &configfile.ConfigFile{}
ctx, err := ResolveDefaultContext(&cliflags.ClientOptions{
ctx, err := resolveDefaultContext(&cliflags.ClientOptions{
TLS: true,
TLSOptions: &tlsconfig.Options{
CAFile: "./testdata/ca.pem",

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,nakedret,unused // ignore linting errors, so that we can stick close to upstream
//nolint:gocyclo,gofumpt,nakedret,unused // ignore linting errors, so that we can stick close to upstream
package tabwriter
import (

View File

@ -10,7 +10,6 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/distribution/reference"
@ -152,7 +151,7 @@ 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")
flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification")
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
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"})
@ -185,7 +184,6 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
//nolint:gocyclo
func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) error {
var (
err error
buildCtx io.ReadCloser
dockerfileCtx io.ReadCloser
contextDir string
@ -267,7 +265,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
}
if err := build.ValidateContextDirectory(contextDir, excludes); err != nil {
return errors.Wrap(err, "error checking context")
return errors.Wrap(err, "checking context")
}
// And canonicalize dockerfile name to a platform-independent one
@ -341,11 +339,19 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
configFile := dockerCli.ConfigFile()
creds, _ := configFile.GetAllCredentials()
authConfigs := make(map[string]registrytypes.AuthConfig, len(creds))
for k, auth := range creds {
authConfigs[k] = registrytypes.AuthConfig(auth)
for k, authConfig := range creds {
authConfigs[k] = registrytypes.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.ServerAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: authConfig.Auth,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
}
}
buildOpts := imageBuildOptions(dockerCli, options)
buildOpts.Version = buildtypes.BuilderV1
buildOpts.Dockerfile = relDockerfile
buildOpts.AuthConfigs = authConfigs
buildOpts.RemoteContext = remote
@ -355,7 +361,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
if options.quiet {
_, _ = fmt.Fprintf(dockerCli.Err(), "%s", progBuff)
}
cancel()
return err
}
defer response.Body.Close()
@ -372,7 +377,8 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
err = jsonstream.Display(ctx, response.Body, streams.NewOut(buildBuff), jsonstream.WithAuxCallback(aux))
if err != nil {
if jerr, ok := err.(*jsonstream.JSONError); ok {
var jerr *jsonstream.JSONError
if errors.As(err, &jerr) {
// If no error code is set, default to 1
if jerr.Code == 0 {
jerr.Code = 1
@ -385,16 +391,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
return err
}
// Windows: show error message about modified file permissions if the
// daemon isn't running Windows.
if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
_, _ = fmt.Fprintln(dockerCli.Out(), "SECURITY WARNING: You are building a Docker "+
"image from Windows against a non-Windows Docker host. All files and "+
"directories added to build context will have '-rwxr-xr-x' permissions. "+
"It is recommended to double check and reset permissions for sensitive "+
"files and directories.")
}
// Everything worked so if -q was provided the output from the daemon
// should be just the image ID and we'll print that to stdout.
if options.quiet {
@ -546,6 +542,7 @@ func replaceDockerfileForContentTrust(ctx context.Context, inputTarStream io.Rea
func imageBuildOptions(dockerCli command.Cli, options buildOptions) buildtypes.ImageBuildOptions {
configFile := dockerCli.ConfigFile()
return buildtypes.ImageBuildOptions{
Version: buildtypes.BuilderV1,
Memory: options.memory.Value(),
MemorySwap: options.memorySwap.Value(),
Tags: options.tags.GetSlice(),

View File

@ -25,9 +25,14 @@ import (
"github.com/pkg/errors"
)
// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
//
// Deprecated: this const is no longer used and will be removed in the next release.
const DefaultDockerfileName string = "Dockerfile"
const (
// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
DefaultDockerfileName string = "Dockerfile"
// defaultDockerfileName is the Default filename with Docker commands, read by docker build
defaultDockerfileName string = "Dockerfile"
// archiveHeaderSize is the number of bytes in an archive header
archiveHeaderSize = 512
)
@ -80,7 +85,7 @@ func ValidateContextDirectory(srcPath string, excludes []string) error {
if err != nil && os.IsPermission(err) {
return errors.Errorf("no permission to read from '%s'", filePath)
}
currentFile.Close()
_ = currentFile.Close()
}
return nil
})
@ -97,10 +102,21 @@ func filepathMatches(matcher *patternmatcher.PatternMatcher, file string) (bool,
// DetectArchiveReader detects whether the input stream is an archive or a
// Dockerfile and returns a buffered version of input, safe to consume in lieu
// of input. If an archive is detected, isArchive is set to true, and to false
// of input. If an archive is detected, ok is set to true, and to false
// otherwise, in which case it is safe to assume input represents the contents
// of a Dockerfile.
func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, isArchive bool, err error) {
//
// Deprecated: this utility was only used internally, and will be removed in the next release.
func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err error) {
return detectArchiveReader(input)
}
// detectArchiveReader detects whether the input stream is an archive or a
// Dockerfile and returns a buffered version of input, safe to consume in lieu
// of input. If an archive is detected, ok is set to true, and to false
// otherwise, in which case it is safe to assume input represents the contents
// of a Dockerfile.
func detectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err error) {
buf := bufio.NewReader(input)
magic, err := buf.Peek(archiveHeaderSize * 2)
@ -108,13 +124,22 @@ func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, isArchive bool,
return nil, false, errors.Errorf("failed to peek context header from STDIN: %v", err)
}
return newReadCloserWrapper(buf, func() error { return input.Close() }), IsArchive(magic), nil
return newReadCloserWrapper(buf, func() error { return input.Close() }), isArchive(magic), nil
}
// WriteTempDockerfile writes a Dockerfile stream to a temporary file with a
// name specified by DefaultDockerfileName and returns the path to the
// name specified by defaultDockerfileName and returns the path to the
// temporary directory containing the Dockerfile.
//
// Deprecated: this utility was only used internally, and will be removed in the next release.
func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
return writeTempDockerfile(rc)
}
// writeTempDockerfile writes a Dockerfile stream to a temporary file with a
// name specified by defaultDockerfileName and returns the path to the
// temporary directory containing the Dockerfile.
func writeTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
// err is a named return value, due to the defer call below.
dockerfileDir, err = os.MkdirTemp("", "docker-build-tempdockerfile-")
if err != nil {
@ -126,7 +151,7 @@ func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
}
}()
f, err := os.Create(filepath.Join(dockerfileDir, DefaultDockerfileName))
f, err := os.Create(filepath.Join(dockerfileDir, defaultDockerfileName))
if err != nil {
return "", err
}
@ -141,12 +166,12 @@ func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
// Dockerfile or tar archive. Returns a tar archive used as a context and a
// path to the Dockerfile inside the tar.
func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
rc, isArchive, err := DetectArchiveReader(rc)
rc, ok, err := detectArchiveReader(rc)
if err != nil {
return nil, "", err
}
if isArchive {
if ok {
return rc, dockerfileName, nil
}
@ -159,7 +184,7 @@ func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadC
return nil, "", errors.New("ambiguous Dockerfile source: both stdin and flag correspond to Dockerfiles")
}
dockerfileDir, err := WriteTempDockerfile(rc)
dockerfileDir, err := writeTempDockerfile(rc)
if err != nil {
return nil, "", err
}
@ -171,14 +196,22 @@ func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadC
return newReadCloserWrapper(tarArchive, func() error {
err := tarArchive.Close()
os.RemoveAll(dockerfileDir)
_ = os.RemoveAll(dockerfileDir)
return err
}), DefaultDockerfileName, nil
}), defaultDockerfileName, nil
}
// IsArchive checks for the magic bytes of a tar or any supported compression
// algorithm.
//
// Deprecated: this utility was used internally and will be removed in the next release.
func IsArchive(header []byte) bool {
return isArchive(header)
}
// isArchive checks for the magic bytes of a tar or any supported compression
// algorithm.
func isArchive(header []byte) bool {
if compression.Detect(header) != compression.None {
return true
}
@ -201,7 +234,7 @@ func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error)
return "", "", errors.Wrapf(err, "unable to 'git clone' to temporary context directory")
}
absContextDir, err = ResolveAndValidateContextPath(absContextDir)
absContextDir, err = resolveAndValidateContextPath(absContextDir)
if err != nil {
return "", "", err
}
@ -242,7 +275,7 @@ func getWithStatusError(url string) (resp *http.Response, err error) {
}
msg := fmt.Sprintf("failed to GET %s with status %s", url, resp.Status)
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
_ = resp.Body.Close()
if err != nil {
return nil, errors.Wrapf(err, "%s: error reading body", msg)
}
@ -254,7 +287,7 @@ func getWithStatusError(url string) (resp *http.Response, err error) {
// the relative path of the dockerfile in that context directory, and a non-nil
// error on success.
func GetContextFromLocalDir(localDir, dockerfileName string) (string, string, error) {
localDir, err := ResolveAndValidateContextPath(localDir)
localDir, err := resolveAndValidateContextPath(localDir)
if err != nil {
return "", "", err
}
@ -274,7 +307,18 @@ func GetContextFromLocalDir(localDir, dockerfileName string) (string, string, er
// ResolveAndValidateContextPath uses the given context directory for a `docker build`
// and returns the absolute path to the context directory.
//
// Deprecated: this utility was used internally and will be removed in the next
// release. Use [DetectContextType] to detect the context-type, and use
// [GetContextFromLocalDir], [GetContextFromLocalDir], [GetContextFromGitURL],
// or [GetContextFromURL] instead.
func ResolveAndValidateContextPath(givenContextDir string) (string, error) {
return resolveAndValidateContextPath(givenContextDir)
}
// resolveAndValidateContextPath uses the given context directory for a `docker build`
// and returns the absolute path to the context directory.
func resolveAndValidateContextPath(givenContextDir string) (string, error) {
absContextDir, err := filepath.Abs(givenContextDir)
if err != nil {
return "", errors.Errorf("unable to get absolute context directory of given context directory %q: %v", givenContextDir, err)
@ -318,12 +362,12 @@ func getDockerfileRelPath(absContextDir, givenDockerfile string) (string, error)
if absDockerfile == "" {
// No -f/--file was specified so use the default relative to the
// context directory.
absDockerfile = filepath.Join(absContextDir, DefaultDockerfileName)
absDockerfile = filepath.Join(absContextDir, defaultDockerfileName)
// Just to be nice ;-) look for 'dockerfile' too but only
// use it if we found it, otherwise ignore this check
if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
altPath := filepath.Join(absContextDir, strings.ToLower(DefaultDockerfileName))
altPath := filepath.Join(absContextDir, strings.ToLower(defaultDockerfileName))
if _, err = os.Lstat(altPath); err == nil {
absDockerfile = altPath
}
@ -374,7 +418,7 @@ func isUNC(path string) bool {
// the relative path to the dockerfile in the context.
func AddDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCloser) (io.ReadCloser, string, error) {
file, err := io.ReadAll(dockerfileCtx)
dockerfileCtx.Close()
_ = dockerfileCtx.Close()
if err != nil {
return nil, "", err
}
@ -438,17 +482,19 @@ func Compress(buildCtx io.ReadCloser) (io.ReadCloser, error) {
go func() {
compressWriter, err := compression.CompressStream(pipeWriter, archive.Gzip)
if err != nil {
pipeWriter.CloseWithError(err)
_ = pipeWriter.CloseWithError(err)
}
defer buildCtx.Close()
defer func() {
_ = buildCtx.Close()
}()
if _, err := io.Copy(compressWriter, buildCtx); err != nil {
pipeWriter.CloseWithError(errors.Wrap(err, "failed to compress context"))
compressWriter.Close()
_ = pipeWriter.CloseWithError(errors.Wrap(err, "failed to compress context"))
_ = compressWriter.Close()
return
}
compressWriter.Close()
pipeWriter.Close()
_ = compressWriter.Close()
_ = pipeWriter.Close()
}()
return pipeReader, nil

View File

@ -31,7 +31,7 @@ func prepareNoFiles(t *testing.T) string {
func prepareOneFile(t *testing.T) string {
t.Helper()
contextDir := createTestTempDir(t)
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
return contextDir
}
@ -66,7 +66,7 @@ func TestGetContextFromLocalDirNotExistingDockerfile(t *testing.T) {
func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) {
contextDir := createTestTempDir(t)
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
chdir(t, contextDir)
@ -74,23 +74,23 @@ func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) {
assert.NilError(t, err)
assert.Check(t, is.Equal(contextDir, absContextDir))
assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile))
assert.Check(t, is.Equal(defaultDockerfileName, relDockerfile))
}
func TestGetContextFromLocalDirWithDockerfile(t *testing.T) {
contextDir := createTestTempDir(t)
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
assert.NilError(t, err)
assert.Check(t, is.Equal(contextDir, absContextDir))
assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile))
assert.Check(t, is.Equal(defaultDockerfileName, relDockerfile))
}
func TestGetContextFromLocalDirLocalFile(t *testing.T) {
contextDir := createTestTempDir(t)
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
testFilename := createTestTempFile(t, contextDir, "tmpTest", "test")
absContextDir, relDockerfile, err := GetContextFromLocalDir(testFilename, "")
@ -112,13 +112,13 @@ func TestGetContextFromLocalDirWithCustomDockerfile(t *testing.T) {
contextDir := createTestTempDir(t)
chdir(t, contextDir)
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, DefaultDockerfileName)
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, defaultDockerfileName)
assert.NilError(t, err)
assert.Check(t, is.Equal(contextDir, absContextDir))
assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile))
assert.Check(t, is.Equal(defaultDockerfileName, relDockerfile))
}
func TestGetContextFromReaderString(t *testing.T) {
@ -135,7 +135,7 @@ func TestGetContextFromReaderString(t *testing.T) {
}
buff := new(bytes.Buffer)
buff.ReadFrom(tarReader)
_, _ = buff.ReadFrom(tarReader)
contents := buff.String()
_, err = tarReader.Next()
@ -150,8 +150,8 @@ func TestGetContextFromReaderString(t *testing.T) {
t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents)
}
if relDockerfile != DefaultDockerfileName {
t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile)
if relDockerfile != defaultDockerfileName {
t.Fatalf("Relative path not equals %s, got: %s", defaultDockerfileName, relDockerfile)
}
}
@ -164,12 +164,12 @@ func TestGetContextFromReaderStringConflict(t *testing.T) {
func TestGetContextFromReaderTar(t *testing.T) {
contextDir := createTestTempDir(t)
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
tarStream, err := archive.Tar(contextDir, compression.None)
assert.NilError(t, err)
tarArchive, relDockerfile, err := GetContextFromReader(tarStream, DefaultDockerfileName)
tarArchive, relDockerfile, err := GetContextFromReader(tarStream, defaultDockerfileName)
assert.NilError(t, err)
tarReader := tar.NewReader(tarArchive)
@ -177,12 +177,12 @@ func TestGetContextFromReaderTar(t *testing.T) {
header, err := tarReader.Next()
assert.NilError(t, err)
if header.Name != DefaultDockerfileName {
t.Fatalf("Dockerfile name should be: %s, got: %s", DefaultDockerfileName, header.Name)
if header.Name != defaultDockerfileName {
t.Fatalf("Dockerfile name should be: %s, got: %s", defaultDockerfileName, header.Name)
}
buff := new(bytes.Buffer)
buff.ReadFrom(tarReader)
_, _ = buff.ReadFrom(tarReader)
contents := buff.String()
_, err = tarReader.Next()
@ -197,8 +197,8 @@ func TestGetContextFromReaderTar(t *testing.T) {
t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents)
}
if relDockerfile != DefaultDockerfileName {
t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile)
if relDockerfile != defaultDockerfileName {
t.Fatalf("Relative path not equals %s, got: %s", defaultDockerfileName, relDockerfile)
}
}
@ -223,7 +223,7 @@ func TestValidateContextDirectoryWithOneFile(t *testing.T) {
}
func TestValidateContextDirectoryWithOneFileExcludes(t *testing.T) {
testValidateContextDirectory(t, prepareOneFile, []string{DefaultDockerfileName})
testValidateContextDirectory(t, prepareOneFile, []string{defaultDockerfileName})
}
// createTestTempDir creates a temporary directory for testing. It returns the
@ -263,7 +263,7 @@ func chdir(t *testing.T, dir string) {
}
func TestIsArchive(t *testing.T) {
testcases := []struct {
tests := []struct {
doc string
header []byte
expected bool
@ -289,13 +289,15 @@ func TestIsArchive(t *testing.T) {
expected: false,
},
}
for _, testcase := range testcases {
assert.Check(t, is.Equal(testcase.expected, IsArchive(testcase.header)), testcase.doc)
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
assert.Check(t, is.Equal(tc.expected, isArchive(tc.header)), tc.doc)
})
}
}
func TestDetectArchiveReader(t *testing.T) {
testcases := []struct {
tests := []struct {
file string
desc string
expected bool
@ -316,14 +318,18 @@ func TestDetectArchiveReader(t *testing.T) {
expected: false,
},
}
for _, testcase := range testcases {
content, err := os.Open(testcase.file)
assert.NilError(t, err)
defer content.Close()
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
content, err := os.Open(tc.file)
assert.NilError(t, err)
defer func() {
_ = content.Close()
}()
_, isArchive, err := DetectArchiveReader(content)
assert.NilError(t, err)
assert.Check(t, is.Equal(testcase.expected, isArchive), testcase.file)
_, isArchive, err := detectArchiveReader(content)
assert.NilError(t, err)
assert.Check(t, is.Equal(tc.expected, isArchive), tc.file)
})
}
}

View File

@ -21,7 +21,9 @@ func ReadDockerignore(contextDir string) ([]string, error) {
case err != nil:
return nil, err
}
defer f.Close()
defer func() {
_ = f.Close()
}()
patterns, err := ignorefile.ReadAll(f)
if err != nil {

View File

@ -42,7 +42,7 @@ func newLoadCommand(dockerCLI command.Cli) *cobra.Command {
Annotations: map[string]string{
"aliases": "docker image load, docker load",
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -8,7 +8,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/internal/prompt"
"github.com/docker/cli/opts"
"github.com/docker/go-units"
@ -49,7 +48,7 @@ func newPruneCommand(dockerCLI command.Cli) *cobra.Command {
return nil
},
Annotations: map[string]string{"version": "1.25"},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -2,15 +2,14 @@ package image
import (
"context"
"errors"
"fmt"
"strings"
"github.com/distribution/reference"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/trust"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -46,7 +45,9 @@ func newPullCommand(dockerCLI command.Cli) *cobra.Command {
"category-top": "5",
"aliases": "docker image pull, docker pull",
},
ValidArgsFunction: completion.NoComplete,
// Complete with local images to help pulling the latest version
// of images that are in the image cache.
ValidArgsFunction: completion.ImageNames(dockerCLI, 1),
}
flags := cmd.Flags()
@ -55,7 +56,7 @@ func newPullCommand(dockerCLI command.Cli) *cobra.Command {
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output")
addPlatformFlag(flags, &opts.platform)
flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification")
flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
@ -85,15 +86,13 @@ func runPull(ctx context.Context, dockerCLI command.Cli, opts pullOptions) error
// Check if reference has a digest
_, isCanonical := distributionRef.(reference.Canonical)
if !opts.untrusted && !isCanonical {
err = trustedPull(ctx, dockerCLI, imgRefAndAuth, opts)
} else {
err = imagePullPrivileged(ctx, dockerCLI, imgRefAndAuth, opts)
}
if err != nil {
if strings.Contains(err.Error(), "when fetching 'plugin'") {
return errors.New(err.Error() + " - Use `docker plugin install`")
if err := trustedPull(ctx, dockerCLI, imgRefAndAuth, opts); err != nil {
return err
}
} else {
if err := imagePullPrivileged(ctx, dockerCLI, imgRefAndAuth, opts); err != nil {
return err
}
return err
}
_, _ = fmt.Fprintln(dockerCLI.Out(), imgRefAndAuth.Reference().String())
return nil

View File

@ -118,11 +118,12 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("DOCKER_CONTENT_TRUST", "true")
cli := test.NewFakeCli(&fakeClient{
imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("")), errors.New("shouldn't try to pull image")
},
}, test.EnableContentTrust)
})
cli.SetNotaryClient(tc.notaryFunc)
cmd := newPullCommand(cli)
cmd.SetOut(io.Discard)

View File

@ -15,12 +15,11 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/jsonstream"
"github.com/docker/cli/internal/registry"
"github.com/docker/cli/internal/tui"
"github.com/docker/docker/api/types/auxprogress"
"github.com/docker/docker/api/types/image"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/morikuni/aec"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
@ -64,7 +63,7 @@ func newPushCommand(dockerCLI command.Cli) *cobra.Command {
flags := cmd.Flags()
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Push all tags of an image to the repository")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output")
flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image signing")
flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image signing")
// Don't default to DOCKER_DEFAULT_PLATFORM env variable, always default to
// pushing the image as-is. This also avoids forcing the platform selection
@ -99,9 +98,11 @@ To push the complete multi-platform image, remove the --platform flag.
}
ref, err := reference.ParseNormalizedNamed(opts.remote)
switch {
case err != nil:
if err != nil {
return err
}
switch {
case opts.all && !reference.IsNameOnly(ref):
return errors.New("tag can't be used with --all-tags/-a")
case !opts.all && reference.IsNameOnly(ref):
@ -111,43 +112,37 @@ To push the complete multi-platform image, remove the --platform flag.
}
}
// Resolve the Repository name from fqn to RepositoryInfo
indexInfo := registry.NewIndexInfo(ref)
// Resolve the Auth config relevant for this server
authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), indexInfo)
encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), ref.String())
if err != nil {
return err
}
options := image.PushOptions{
responseBody, err := dockerCli.Client().ImagePush(ctx, reference.FamiliarString(ref), image.PushOptions{
All: opts.all,
RegistryAuth: encodedAuth,
PrivilegeFunc: nil,
Platform: platform,
}
responseBody, err := dockerCli.Client().ImagePush(ctx, reference.FamiliarString(ref), options)
})
if err != nil {
return err
}
defer func() {
_ = responseBody.Close()
for _, note := range notes {
out.PrintNote(note)
}
}()
defer responseBody.Close()
if !opts.untrusted {
// TODO pushTrustedReference currently doesn't respect `--quiet`
return pushTrustedReference(ctx, dockerCli, indexInfo, ref, authConfig, responseBody)
return pushTrustedReference(ctx, dockerCli, ref, responseBody)
}
if opts.quiet {
err = jsonstream.Display(ctx, responseBody, streams.NewOut(io.Discard), jsonstream.WithAuxCallback(handleAux()))
if err == nil {
fmt.Fprintln(dockerCli.Out(), ref.String())
_, _ = fmt.Fprintln(dockerCli.Out(), ref.String())
}
return err
}

View File

@ -12,6 +12,7 @@ import (
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/jsonstream"
"github.com/docker/cli/internal/registry"
"github.com/docker/docker/api/types/image"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/opencontainers/go-digest"
@ -42,12 +43,18 @@ func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth) (
}
// pushTrustedReference pushes a canonical reference to the trust server.
func pushTrustedReference(ctx context.Context, ioStreams command.Streams, indexInfo *registrytypes.IndexInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader) error {
func pushTrustedReference(ctx context.Context, dockerCLI command.Cli, ref reference.Named, responseBody io.Reader) error {
// Resolve the Repository name from fqn to RepositoryInfo, and create an
// IndexInfo. Docker Content Trust uses the IndexInfo.Official field to
// select the right domain for Docker Hub's Notary server;
// https://github.com/docker/cli/blob/v28.4.0/cli/trust/trust.go#L65-L79
indexInfo := registry.NewIndexInfo(ref)
repoInfo := &trust.RepositoryInfo{
Name: reference.TrimNamed(ref),
Index: indexInfo,
}
return trust.PushTrustedReference(ctx, ioStreams, repoInfo, ref, authConfig, in, command.UserAgent())
authConfig := command.ResolveAuthConfig(dockerCLI.ConfigFile(), indexInfo)
return trust.PushTrustedReference(ctx, dockerCLI, repoInfo, ref, authConfig, responseBody, command.UserAgent())
}
// trustedPull handles content trust pulling of an image

View File

@ -5,6 +5,7 @@ import (
"fmt"
"path/filepath"
"github.com/containerd/errdefs"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config"
@ -54,6 +55,7 @@ func newRegistryClient(dockerCLI command.Cli, allowInsecure bool) registryclient
resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig {
return command.ResolveAuthConfig(dockerCLI.ConfigFile(), index)
}
// FIXME(thaJeztah): this should use the userAgent as configured on the dockerCLI.
return registryclient.NewRegistryClient(resolver, command.UserAgent(), allowInsecure)
}
@ -96,7 +98,7 @@ func runManifestAnnotate(dockerCLI command.Cli, opts annotateOptions) error {
manifestStore := newManifestStore(dockerCLI)
imageManifest, err := manifestStore.Get(targetRef, imgRef)
switch {
case store.IsNotFound(err):
case errdefs.IsNotFound(err):
return fmt.Errorf("manifest for image %s does not exist in %s", opts.image, opts.target)
case err != nil:
return err

View File

@ -4,9 +4,9 @@ import (
"context"
"fmt"
"github.com/containerd/errdefs"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/store"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -44,7 +44,7 @@ func createManifestList(ctx context.Context, dockerCLI command.Cli, args []strin
manifestStore := newManifestStore(dockerCLI)
_, err = manifestStore.GetList(targetRef)
switch {
case store.IsNotFound(err):
case errdefs.IsNotFound(err):
// New manifest list
case err != nil:
return err

View File

@ -3,9 +3,9 @@ package manifest
import (
"context"
"github.com/containerd/errdefs"
"github.com/distribution/reference"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/store"
"github.com/docker/cli/cli/manifest/types"
)
@ -72,7 +72,7 @@ func normalizeReference(ref string) (reference.Named, error) {
func getManifest(ctx context.Context, dockerCLI command.Cli, listRef, namedRef reference.Named, insecure bool) (types.ImageManifest, error) {
data, err := newManifestStore(dockerCLI).Get(listRef, namedRef)
switch {
case store.IsNotFound(err):
case errdefs.IsNotFound(err):
return newRegistryClient(dockerCLI, insecure).GetManifest(ctx, namedRef)
case err != nil:
return types.ImageManifest{}, err

View File

@ -10,7 +10,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
@ -69,7 +68,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
return runCreate(cmd.Context(), dockerCLI.Client(), dockerCLI.Out(), options)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -6,7 +6,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/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
@ -33,7 +32,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runList(cmd.Context(), dockerCli, options)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -6,7 +6,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/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
@ -34,7 +33,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runList(cmd.Context(), dockerCli, options)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
@ -45,7 +44,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
})
return cmd
}

View File

@ -6,7 +6,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/command/idresolver"
"github.com/docker/cli/cli/command/task"
"github.com/docker/cli/opts"
@ -54,7 +53,7 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
})
return cmd
}

View File

@ -43,7 +43,7 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
})
return cmd
}

View File

@ -10,7 +10,6 @@ import (
"github.com/distribution/reference"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types"
"github.com/moby/go-archive"
"github.com/moby/go-archive/compression"
@ -76,7 +75,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
options.context = args[1]
return runCreate(cmd.Context(), dockerCli, options)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -3,12 +3,12 @@ package plugin
import (
"context"
"fmt"
"strings"
"github.com/distribution/reference"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/jsonstream"
"github.com/docker/cli/internal/prompt"
"github.com/docker/cli/internal/registry"
@ -29,9 +29,9 @@ type pluginOptions struct {
untrusted bool
}
func loadPullFlags(dockerCli command.Cli, opts *pluginOptions, flags *pflag.FlagSet) {
func loadPullFlags(opts *pluginOptions, flags *pflag.FlagSet) {
flags.BoolVar(&opts.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin")
flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification")
flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
}
func newInstallCommand(dockerCli command.Cli) *cobra.Command {
@ -50,7 +50,7 @@ func newInstallCommand(dockerCli command.Cli) *cobra.Command {
}
flags := cmd.Flags()
loadPullFlags(dockerCli, &options, flags)
loadPullFlags(&options, flags)
flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install")
flags.StringVar(&options.localName, "alias", "", "Local name for plugin")
return cmd
@ -120,9 +120,6 @@ func runInstall(ctx context.Context, dockerCLI command.Cli, opts pluginOptions)
}
responseBody, err := dockerCLI.Client().PluginInstall(ctx, localName, options)
if err != nil {
if strings.Contains(err.Error(), "(image) when fetching") {
return errors.New(err.Error() + " - Use \"docker image pull\"")
}
return err
}
defer func() {

View File

@ -43,14 +43,6 @@ func TestInstallErrors(t *testing.T) {
return nil, errors.New("error installing plugin")
},
},
{
description: "installation error due to missing image",
args: []string{"foo"},
expectedError: "docker image pull",
installFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
return nil, errors.New("(image) when fetching")
},
},
}
for _, tc := range testCases {
@ -94,11 +86,12 @@ func TestInstallContentTrustErrors(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
t.Setenv("DOCKER_CONTENT_TRUST", "true")
cli := test.NewFakeCli(&fakeClient{
pluginInstallFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
return nil, errors.New("should not try to install plugin")
},
}, test.EnableContentTrust)
})
cli.SetNotaryClient(tc.notaryFunc)
cmd := newInstallCommand(cli)
cmd.SetArgs(tc.args)

View File

@ -6,7 +6,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/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
@ -32,7 +31,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runList(cmd.Context(), dockerCli, options)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -33,7 +33,7 @@ func newPushCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags()
flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image signing")
flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image signing")
return cmd
}

View File

@ -3,7 +3,6 @@ package plugin
import (
"context"
"fmt"
"strings"
"github.com/distribution/reference"
"github.com/docker/cli/cli"
@ -31,7 +30,7 @@ func newUpgradeCommand(dockerCli command.Cli) *cobra.Command {
}
flags := cmd.Flags()
loadPullFlags(dockerCli, &options, flags)
loadPullFlags(&options, flags)
flags.BoolVar(&options.skipRemoteCheck, "skip-remote-check", false, "Do not check if specified remote plugin matches existing plugin image")
return cmd
}
@ -80,9 +79,6 @@ func runUpgrade(ctx context.Context, dockerCLI command.Cli, opts pluginOptions)
responseBody, err := dockerCLI.Client().PluginUpgrade(ctx, opts.localName, options)
if err != nil {
if strings.Contains(err.Error(), "target is image") {
return errors.New(err.Error() + " - Use `docker image pull`")
}
return err
}
defer func() {

View File

@ -77,7 +77,16 @@ func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInf
}
a, _ := cfg.GetAuthConfig(configKey)
return registrytypes.AuthConfig(a)
return registrytypes.AuthConfig{
Username: a.Username,
Password: a.Password,
ServerAddress: a.ServerAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: a.Auth,
IdentityToken: a.IdentityToken,
RegistryToken: a.RegistryToken,
}
}
// GetDefaultAuthConfig gets the default auth config given a serverAddress
@ -86,19 +95,27 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve
if !isDefaultRegistry {
serverAddress = credentials.ConvertToHostname(serverAddress)
}
authconfig := configtypes.AuthConfig{}
authCfg := configtypes.AuthConfig{}
var err error
if checkCredStore {
authconfig, err = cfg.GetAuthConfig(serverAddress)
authCfg, err = cfg.GetAuthConfig(serverAddress)
if err != nil {
return registrytypes.AuthConfig{
ServerAddress: serverAddress,
}, err
}
}
authconfig.ServerAddress = serverAddress
authconfig.IdentityToken = ""
return registrytypes.AuthConfig(authconfig), nil
return registrytypes.AuthConfig{
Username: authCfg.Username,
Password: authCfg.Password,
ServerAddress: serverAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: authCfg.Auth,
IdentityToken: "",
RegistryToken: authCfg.RegistryToken,
}, nil
}
// PromptUserForCredentials handles the CLI prompt for the user to input
@ -213,7 +230,16 @@ func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (strin
return "", err
}
encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig))
encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.ServerAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: authConfig.Auth,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
})
if err != nil {
return "", err
}

View File

@ -11,7 +11,6 @@ import (
"github.com/containerd/errdefs"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/config/configfile"
configtypes "github.com/docker/cli/cli/config/types"
"github.com/docker/cli/internal/oauth/manager"
@ -59,7 +58,7 @@ func newLoginCommand(dockerCLI command.Cli) *cobra.Command {
Annotations: map[string]string{
"category-top": "8",
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()
@ -185,10 +184,15 @@ func loginWithStoredCredentials(ctx context.Context, dockerCLI command.Cli, auth
return response.Status, err
}
// OauthLoginEscapeHatchEnvVar disables the browser-based OAuth login workflow.
//
// Deprecated: this const was only used internally and will be removed in the next release.
const OauthLoginEscapeHatchEnvVar = "DOCKER_CLI_DISABLE_OAUTH_LOGIN"
const oauthLoginEscapeHatchEnvVar = "DOCKER_CLI_DISABLE_OAUTH_LOGIN"
func isOauthLoginDisabled() bool {
if v := os.Getenv(OauthLoginEscapeHatchEnvVar); v != "" {
if v := os.Getenv(oauthLoginEscapeHatchEnvVar); v != "" {
enabled, err := strconv.ParseBool(v)
if err != nil {
return false
@ -255,12 +259,30 @@ func loginWithDeviceCodeFlow(ctx context.Context, dockerCLI command.Cli) (msg st
return "", err
}
response, err := loginWithRegistry(ctx, dockerCLI.Client(), registrytypes.AuthConfig(*authConfig))
response, err := loginWithRegistry(ctx, dockerCLI.Client(), registrytypes.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.ServerAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: authConfig.Auth,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
})
if err != nil {
return "", err
}
if err = storeCredentials(dockerCLI.ConfigFile(), registrytypes.AuthConfig(*authConfig)); err != nil {
if err = storeCredentials(dockerCLI.ConfigFile(), registrytypes.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.ServerAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: authConfig.Auth,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
}); err != nil {
return "", err
}
@ -269,7 +291,16 @@ func loginWithDeviceCodeFlow(ctx context.Context, dockerCLI command.Cli) (msg st
func storeCredentials(cfg *configfile.ConfigFile, authConfig registrytypes.AuthConfig) error {
creds := cfg.GetCredentialsStore(authConfig.ServerAddress)
if err := creds.Store(configtypes.AuthConfig(authConfig)); err != nil {
if err := creds.Store(configtypes.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.ServerAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: authConfig.Auth,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
}); err != nil {
return errors.Errorf("Error saving credentials: %v", err)
}

View File

@ -533,7 +533,7 @@ func TestIsOauthLoginDisabled(t *testing.T) {
}
for _, tc := range testCases {
t.Setenv(OauthLoginEscapeHatchEnvVar, tc.envVar)
t.Setenv(oauthLoginEscapeHatchEnvVar, tc.envVar)
disabled := isOauthLoginDisabled()

View File

@ -102,7 +102,16 @@ func getAuth(dockerCLI command.Cli, reposName string) (encodedAuth string, err e
// "no credentials found"). We'll get an error when search failed,
// so fine to ignore in most situations.
authConfig, _ := dockerCLI.ConfigFile().GetAuthConfig(authCfgKey)
return registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig))
return registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.ServerAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: authConfig.Auth,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
})
}
// splitReposSearchTerm breaks a search term into an index name and remote name

View File

@ -58,8 +58,17 @@ func TestGetDefaultAuthConfig(t *testing.T) {
},
}
cfg := configfile.New("filename")
for _, authconfig := range testAuthConfigs {
assert.Check(t, cfg.GetCredentialsStore(authconfig.ServerAddress).Store(configtypes.AuthConfig(authconfig)))
for _, authConfig := range testAuthConfigs {
assert.Check(t, cfg.GetCredentialsStore(authConfig.ServerAddress).Store(configtypes.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.ServerAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: authConfig.Auth,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
}))
}
for _, tc := range testCases {
serverAddress := tc.inputServerAddress

View File

@ -29,7 +29,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
}
return runCreate(cmd.Context(), dockerCLI, cmd.Flags(), opts)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()
flags.StringVar(&opts.mode, flagMode, "replicated", `Service mode ("replicated", "global", "replicated-job", "global-job")`)
@ -94,7 +94,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
})
return cmd
}

View File

@ -10,7 +10,6 @@ import (
"github.com/containerd/errdefs"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/docker/api/types/network"
@ -52,7 +51,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
})
return cmd
}

View File

@ -5,7 +5,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/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
@ -33,7 +32,7 @@ func newListCommand(dockerCLI command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runList(cmd.Context(), dockerCLI, options)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()
@ -45,7 +44,7 @@ func newListCommand(dockerCLI command.Cli) *cobra.Command {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
})
return cmd
}

View File

@ -12,7 +12,6 @@ import (
"github.com/containerd/errdefs"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/internal/logdetails"
@ -73,7 +72,7 @@ func newLogsCommand(dockerCli command.Cli) *cobra.Command {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
})
return cmd
}

View File

@ -6,7 +6,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/command/idresolver"
"github.com/docker/cli/cli/command/node"
"github.com/docker/cli/cli/command/task"
@ -52,7 +51,7 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
})
return cmd
}

View File

@ -6,7 +6,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/spf13/cobra"
@ -35,7 +34,7 @@ func newRollbackCommand(dockerCli command.Cli) *cobra.Command {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
})
return cmd
}

View File

@ -15,7 +15,7 @@ import (
)
func resolveServiceImageDigestContentTrust(dockerCli command.Cli, service *swarm.ServiceSpec) error {
if !dockerCli.ContentTrustEnabled() {
if !trust.Enabled() {
// When not using content trust, digest resolution happens later when
// contacting the registry to retrieve image information.
return nil

View File

@ -137,7 +137,7 @@ func newUpdateCommand(dockerCLI command.Cli) *cobra.Command {
// Set a default completion function if none was set. We don't look
// up if it does already have one set, because Cobra does this for
// us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc(flag.Name, cobra.NoFileCompletions)
})
return cmd

View File

@ -6,7 +6,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/command/stack/loader"
"github.com/docker/cli/cli/command/stack/options"
composeLoader "github.com/docker/cli/cli/compose/loader"
@ -36,7 +35,7 @@ func newConfigCommand(dockerCli command.Cli) *cobra.Command {
_, err = fmt.Fprintf(dockerCli.Out(), "%s", cfg)
return err
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -7,7 +7,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/command/stack/formatter"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/command/stack/swarm"
@ -29,7 +28,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runList(cmd.Context(), dockerCli, opts)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -8,7 +8,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/command/swarm/progress"
"github.com/docker/cli/internal/jsonstream"
"github.com/docker/docker/api/types/swarm"
@ -40,7 +39,7 @@ func newCACommand(dockerCli command.Cli) *cobra.Command {
"version": "1.30",
"swarm": "manager",
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -8,7 +8,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -44,7 +43,7 @@ func newInitCommand(dockerCli command.Cli) *cobra.Command {
"version": "1.24",
"swarm": "", // swarm init does not require swarm to be active, and is always available on API 1.24 and up
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -6,7 +6,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/spf13/cobra"
)
@ -28,7 +27,7 @@ func newLeaveCommand(dockerCli command.Cli) *cobra.Command {
"version": "1.24",
"swarm": "active",
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -9,7 +9,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/streams"
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"
@ -29,7 +28,7 @@ func newUnlockCommand(dockerCli command.Cli) *cobra.Command {
"version": "1.24",
"swarm": "manager",
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
return cmd

View File

@ -7,7 +7,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -32,7 +31,7 @@ func newUnlockKeyCommand(dockerCli command.Cli) *cobra.Command {
"version": "1.24",
"swarm": "manager",
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -6,7 +6,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -33,7 +32,7 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
"version": "1.24",
"swarm": "manager",
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
cmd.Flags().BoolVar(&opts.autolock, flagAutolock, false, "Change manager autolocking setting (true|false)")

View File

@ -5,7 +5,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/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/docker/api/types"
@ -29,7 +28,7 @@ func newDiskUsageCommand(dockerCli command.Cli) *cobra.Command {
return runDiskUsage(cmd.Context(), dockerCli, opts)
},
Annotations: map[string]string{"version": "1.25"},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

View File

@ -7,7 +7,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@ -23,7 +22,7 @@ func newDialStdioCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runDialStdio(cmd.Context(), dockerCli)
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
return cmd
}

View File

@ -11,7 +11,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/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
@ -48,7 +47,7 @@ func newEventsCommand(dockerCLI command.Cli) *cobra.Command {
Annotations: map[string]string{
"aliases": "docker system events, docker events",
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: cobra.NoFileCompletions,
}
flags := cmd.Flags()

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