mirror of
https://github.com/docker/cli.git
synced 2026-11-12 15:53:52 -04:00
Compare commits
293
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecc694264d | ||
|
|
c475c696cf | ||
|
|
7494d2cee4 | ||
|
|
5306df36fa | ||
|
|
5dbaa52325 | ||
|
|
dd832b6d97 | ||
|
|
a99e91cc89 | ||
|
|
579b72aa06 | ||
|
|
6fc5891770 | ||
|
|
9af6cbc489 | ||
|
|
ba53322412 | ||
|
|
61d2e8aaad | ||
|
|
6abcd4a2a1 | ||
|
|
00c129b974 | ||
|
|
fbf5c8a86d | ||
|
|
4c7d52534f | ||
|
|
70123c523c | ||
|
|
4942b2747f | ||
|
|
d8bab71747 | ||
|
|
36d9523e31 | ||
|
|
7063a0ef07 | ||
|
|
22487ad6f6 | ||
|
|
4737ed4906 | ||
|
|
8cff0087da | ||
|
|
e8b22ce03c | ||
|
|
e180ab8ab8 | ||
|
|
0d799c556f | ||
|
|
887030fbe8 | ||
|
|
9c6a0e0ba9 | ||
|
|
f784471104 | ||
|
|
d7afcf9b98 | ||
|
|
9d9adf6346 | ||
|
|
d4b7734f18 | ||
|
|
a1061611fd | ||
|
|
5e42f826b4 | ||
|
|
645c23bd13 | ||
|
|
e491078fc6 | ||
|
|
a3ffb8a148 | ||
|
|
a4ae5f2f7a | ||
|
|
394991e2ab | ||
|
|
e5bce5cd2d | ||
|
|
d5c181abf4 | ||
|
|
0351ece9e5 | ||
|
|
ec00b85794 | ||
|
|
a69c591c5a | ||
|
|
f9d2820a20 | ||
|
|
c28ec0e4ce | ||
|
|
d1c8336395 | ||
|
|
61b9fd4068 | ||
|
|
2ef1b4eabe | ||
|
|
bea31fccbb | ||
|
|
e9c189e1c2 | ||
|
|
118548d02b | ||
|
|
026ef0df2d | ||
|
|
4665091715 | ||
|
|
261d8bcf8d | ||
|
|
3755161455 | ||
|
|
db5a0ae673 | ||
|
|
843153da37 | ||
|
|
a2d7989230 | ||
|
|
985cee2de0 | ||
|
|
e1dd0e1501 | ||
|
|
82ff4b5634 | ||
|
|
4de56bc72f | ||
|
|
2ed0d99acc | ||
|
|
cbeddb1390 | ||
|
|
1fb1577626 | ||
|
|
67885d0dcc | ||
|
|
2ed42a8ade | ||
|
|
4bac500fb2 | ||
|
|
9d9f632527 | ||
|
|
a2e17eb9d5 | ||
|
|
5d201ca436 | ||
|
|
d1122a2293 | ||
|
|
d52de77ef4 | ||
|
|
01f8949484 | ||
|
|
b9e3346b1e | ||
|
|
25421ace0b | ||
|
|
ede7019b14 | ||
|
|
d48256b26d | ||
|
|
09fcf8e3dd | ||
|
|
56f7bd0759 | ||
|
|
3bacc99580 | ||
|
|
f77defa891 | ||
|
|
4a677b86a6 | ||
|
|
1cf78c49ab | ||
|
|
2fb1298416 | ||
|
|
a0ebf3e35c | ||
|
|
428518a84a | ||
|
|
c1914f73f5 | ||
|
|
cab5014d57 | ||
|
|
7c3ec3ec02 | ||
|
|
aa976192cb | ||
|
|
2de1ea9769 | ||
|
|
0ac676b171 | ||
|
|
b627f18262 | ||
|
|
d8eb465f86 | ||
|
|
a83e3df40b | ||
|
|
ff5ea757b4 | ||
|
|
c1d14aef3c | ||
|
|
18adfd54a9 | ||
|
|
6e20b9d93c | ||
|
|
3cadbd82c5 | ||
|
|
b3df92053d | ||
|
|
b89b069082 | ||
|
|
9deb5e9cd3 | ||
|
|
e8be20bc9c | ||
|
|
f2cd1979c0 | ||
|
|
991d942cc3 | ||
|
|
8e49313c0c | ||
|
|
6cd42da462 | ||
|
|
2b9489d827 | ||
|
|
fbeae8516b | ||
|
|
d593e61275 | ||
|
|
139968dff2 | ||
|
|
925db59377 | ||
|
|
aced30d906 | ||
|
|
b9e15efde0 | ||
|
|
4108febfae | ||
|
|
8cfe1f712e | ||
|
|
7c34fd56d0 | ||
|
|
0a2eaa4d05 | ||
|
|
d26f1fd6d3 | ||
|
|
4f6182a50c | ||
|
|
4caa99468d | ||
|
|
c2117f956f | ||
|
|
19b86ef9e2 | ||
|
|
88d6197d4c | ||
|
|
bda15c766a | ||
|
|
e636ed2ae5 | ||
|
|
19b7dfa1cf | ||
|
|
91de2a6dae | ||
|
|
7c6f58affe | ||
|
|
8c6a7fcbc9 | ||
|
|
3e87f59859 | ||
|
|
a621313658 | ||
|
|
69dd67fc06 | ||
|
|
b90c5a8fcb | ||
|
|
99676e6c9c | ||
|
|
524fd156f5 | ||
|
|
07bcb9cca2 | ||
|
|
48ea3fa14f | ||
|
|
a27a86289e | ||
|
|
e112eeea4d | ||
|
|
3b68cde77a | ||
|
|
c712d97172 | ||
|
|
f385cb994f | ||
|
|
44c1d5775d | ||
|
|
1468bb1962 | ||
|
|
44fb305591 | ||
|
|
b74574cd53 | ||
|
|
e60b20f08e | ||
|
|
6f7865f89a | ||
|
|
23518a47dd | ||
|
|
eb120d2c28 | ||
|
|
146f6492c7 | ||
|
|
8def226e53 | ||
|
|
82c5138c8e | ||
|
|
bd3e420cc7 | ||
|
|
bb2ed910bb | ||
|
|
1415080cfa | ||
|
|
0bbdda88e6 | ||
|
|
4133271530 | ||
|
|
0e17907266 | ||
|
|
b0b7dcfe86 | ||
|
|
531ea22e7d | ||
|
|
e8cf8e65f7 | ||
|
|
22c68c5366 | ||
|
|
e424c50373 | ||
|
|
399861e7d2 | ||
|
|
694e92aa10 | ||
|
|
09a36f2ef1 | ||
|
|
73fbe6a020 | ||
|
|
4480024a80 | ||
|
|
b4f2c0ca3d | ||
|
|
85a5e3da36 | ||
|
|
34a75fb6f4 | ||
|
|
b52ab17a14 | ||
|
|
ec89d0ba67 | ||
|
|
aa44c436d7 | ||
|
|
39e1213615 | ||
|
|
5184f2ed65 | ||
|
|
cb42a72704 | ||
|
|
5ef48d620d | ||
|
|
9e08776681 | ||
|
|
85512e35cd | ||
|
|
ce242b0ee8 | ||
|
|
ae3ee9b47b | ||
|
|
733762af0a | ||
|
|
46862878f7 | ||
|
|
4a2b024c57 | ||
|
|
82de0590b6 | ||
|
|
3e36fa624a | ||
|
|
01ea3a7da7 | ||
|
|
0f2721ee4f | ||
|
|
6fc62cf5b6 | ||
|
|
c580c9741e | ||
|
|
ba8b22e783 | ||
|
|
def5bfc11c | ||
|
|
9d258edf27 | ||
|
|
f68a9a06fe | ||
|
|
60caaa39e0 | ||
|
|
c94245da66 | ||
|
|
79c03dcaaf | ||
|
|
acb019a344 | ||
|
|
5a80c3e98d | ||
|
|
e250694a35 | ||
|
|
942eade165 | ||
|
|
a746a99d97 | ||
|
|
66c3dbdfed | ||
|
|
cf44e3164b | ||
|
|
ce3196c726 | ||
|
|
80884714de | ||
|
|
4e00c31c71 | ||
|
|
7126bf7d22 | ||
|
|
53ed958805 | ||
|
|
be307b2925 | ||
|
|
8f8d39c35e | ||
|
|
c61c0cdb3c | ||
|
|
830b5f1d2f | ||
|
|
abfc1f4c76 | ||
|
|
31fa40cb25 | ||
|
|
4881921784 | ||
|
|
4313a2bbb7 | ||
|
|
20c34ef13b | ||
|
|
34a90bdd81 | ||
|
|
98b82d5156 | ||
|
|
0e474e38f1 | ||
|
|
6adde56036 | ||
|
|
7ccd6d29c6 | ||
|
|
14f297204f | ||
|
|
d1617cb0c0 | ||
|
|
7091e8bea4 | ||
|
|
ff42ff9f06 | ||
|
|
bcc479b4c3 | ||
|
|
6a596c007c | ||
|
|
79e0b1d7d9 | ||
|
|
099820d8ca | ||
|
|
ac88b74462 | ||
|
|
7f699066bd | ||
|
|
fe1d790d8e | ||
|
|
5e29918a44 | ||
|
|
09efe3f408 | ||
|
|
79dd3a3c79 | ||
|
|
a8fe4aaa7f | ||
|
|
034dc932d7 | ||
|
|
0718529a7e | ||
|
|
c754ac3c5b | ||
|
|
5d327e8032 | ||
|
|
bce66f6126 | ||
|
|
511821052a | ||
|
|
9fc26d43ed | ||
|
|
2b4e0a0f45 | ||
|
|
5d6e64c4a6 | ||
|
|
ed52ada4a3 | ||
|
|
60d16e20ac | ||
|
|
713ed839fe | ||
|
|
6bfee62d6d | ||
|
|
efdf008933 | ||
|
|
28ffe2416d | ||
|
|
b0bb4acc11 | ||
|
|
2e5a36728b | ||
|
|
a5a17eb2c7 | ||
|
|
e1f79927f3 | ||
|
|
cab6012db9 | ||
|
|
75a4cbbf8e | ||
|
|
9071d3868c | ||
|
|
e7f4f04156 | ||
|
|
7e01a3a8a9 | ||
|
|
72f85ccd16 | ||
|
|
ecd54bc6dd | ||
|
|
8b9baffdf7 | ||
|
|
93a51c39f4 | ||
|
|
2dc9daae32 | ||
|
|
a4f8f22a33 | ||
|
|
7d3bde083c | ||
|
|
32207833d0 | ||
|
|
7399781944 | ||
|
|
efee5879a1 | ||
|
|
b82e19efe0 | ||
|
|
eceff3dbc5 | ||
|
|
3598fc3745 | ||
|
|
2df466710b | ||
|
|
29f2ce760a | ||
|
|
363f4c0031 | ||
|
|
6d4ffec3fb | ||
|
|
5c1cee4630 | ||
|
|
88274f4805 | ||
|
|
5566c3a9b8 | ||
|
|
5edc6748f4 | ||
|
|
6007e83a75 | ||
|
|
ce2a0a4ecb | ||
|
|
3cd108fcd0 |
@@ -35,7 +35,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
-
|
-
|
||||||
name: Create matrix
|
name: Create matrix
|
||||||
id: platforms
|
id: platforms
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Upload artifacts
|
name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: ${{ env.ARTIFACT_NAME }}
|
name: ${{ env.ARTIFACT_NAME }}
|
||||||
path: /tmp/out/*
|
path: /tmp/out/*
|
||||||
@@ -143,7 +143,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
-
|
-
|
||||||
name: Create matrix
|
name: Create matrix
|
||||||
id: platforms
|
id: platforms
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
|
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
|
||||||
@@ -61,19 +61,19 @@ jobs:
|
|||||||
ln -s vendor.sum go.sum
|
ln -s vendor.sum go.sum
|
||||||
-
|
-
|
||||||
name: Update Go
|
name: Update Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.5"
|
go-version: "1.24.9"
|
||||||
-
|
-
|
||||||
name: Initialize CodeQL
|
name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: go
|
languages: go
|
||||||
-
|
-
|
||||||
name: Autobuild
|
name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v3
|
uses: github/codeql-action/autobuild@v4
|
||||||
-
|
-
|
||||||
name: Perform CodeQL Analysis
|
name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v4
|
||||||
with:
|
with:
|
||||||
category: "/language:go"
|
category: "/language:go"
|
||||||
|
|||||||
@@ -39,12 +39,11 @@ jobs:
|
|||||||
engine-version:
|
engine-version:
|
||||||
- 28 # latest
|
- 28 # latest
|
||||||
- 27 # latest - 1
|
- 27 # latest - 1
|
||||||
- 26 # github actions default
|
- 25 # mirantis lts
|
||||||
- 23 # mirantis lts
|
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
-
|
-
|
||||||
name: Update daemon.json
|
name: Update daemon.json
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -53,20 +53,21 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
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
|
# - 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:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.GOPATH }}/src/github.com/docker/cli
|
path: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||||
-
|
-
|
||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.5"
|
go-version: "1.24.9"
|
||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
-
|
-
|
||||||
name: Generate
|
name: Generate
|
||||||
shell: 'script --return --quiet --command "bash {0}"'
|
shell: 'script --return --quiet --command "bash {0}"'
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
-
|
-
|
||||||
name: Run
|
name: Run
|
||||||
shell: 'script --return --quiet --command "bash {0}"'
|
shell: 'script --return --quiet --command "bash {0}"'
|
||||||
|
|||||||
+14
-5
@@ -5,7 +5,7 @@ run:
|
|||||||
# which causes it to fallback to go1.17 semantics.
|
# 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
|
# TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24
|
||||||
go: "1.24.5"
|
go: "1.24.9"
|
||||||
|
|
||||||
timeout: 5m
|
timeout: 5m
|
||||||
|
|
||||||
@@ -86,6 +86,8 @@ linters:
|
|||||||
desc: Use github.com/moby/sys/userns instead.
|
desc: Use github.com/moby/sys/userns instead.
|
||||||
- pkg: "github.com/containerd/containerd/platforms"
|
- pkg: "github.com/containerd/containerd/platforms"
|
||||||
desc: The containerd platforms package was migrated to a separate module. Use github.com/containerd/platforms instead.
|
desc: The containerd platforms package was migrated to a separate module. Use github.com/containerd/platforms instead.
|
||||||
|
- pkg: "github.com/docker/docker/errdefs"
|
||||||
|
desc: Use github.com/containerd/errdefs instead.
|
||||||
- pkg: "github.com/docker/docker/pkg/system"
|
- pkg: "github.com/docker/docker/pkg/system"
|
||||||
desc: This package should not be used unless strictly necessary.
|
desc: This package should not be used unless strictly necessary.
|
||||||
- pkg: "github.com/docker/distribution/uuid"
|
- pkg: "github.com/docker/distribution/uuid"
|
||||||
@@ -124,10 +126,9 @@ linters:
|
|||||||
no-unaliased: true
|
no-unaliased: true
|
||||||
|
|
||||||
alias:
|
alias:
|
||||||
# Enforce alias to prevent it accidentally being used instead of our
|
# Should no longer be aliased, because we no longer allow moby/docker errdefs.
|
||||||
# own errdefs package (or vice-versa).
|
- pkg: "github.com/docker/docker/errdefs"
|
||||||
- pkg: github.com/containerd/errdefs
|
alias: ""
|
||||||
alias: cerrdefs
|
|
||||||
- pkg: github.com/opencontainers/image-spec/specs-go/v1
|
- pkg: github.com/opencontainers/image-spec/specs-go/v1
|
||||||
alias: ocispec
|
alias: ocispec
|
||||||
# Enforce that gotest.tools/v3/assert/cmp is always aliased as "is"
|
# Enforce that gotest.tools/v3/assert/cmp is always aliased as "is"
|
||||||
@@ -221,6 +222,14 @@ linters:
|
|||||||
linters:
|
linters:
|
||||||
- staticcheck
|
- staticcheck
|
||||||
|
|
||||||
|
# Ignore deprecation linting for cli/command/stack/*.
|
||||||
|
#
|
||||||
|
# FIXME(thaJeztah): remove exception once these functions are un-exported or internal; see https://github.com/docker/cli/pull/6389
|
||||||
|
- text: '^(SA1019): '
|
||||||
|
path: "cli/command/stack"
|
||||||
|
linters:
|
||||||
|
- staticcheck
|
||||||
|
|
||||||
# Log a warning if an exclusion rule is unused.
|
# Log a warning if an exclusion rule is unused.
|
||||||
# Default: false
|
# Default: false
|
||||||
warn-unused: true
|
warn-unused: true
|
||||||
|
|||||||
+3
-3
@@ -8,13 +8,13 @@ ARG BASE_VARIANT=alpine
|
|||||||
ARG ALPINE_VERSION=3.21
|
ARG ALPINE_VERSION=3.21
|
||||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||||
|
|
||||||
ARG GO_VERSION=1.24.5
|
ARG GO_VERSION=1.24.9
|
||||||
ARG XX_VERSION=1.6.1
|
ARG XX_VERSION=1.6.1
|
||||||
ARG GOVERSIONINFO_VERSION=v1.4.1
|
ARG GOVERSIONINFO_VERSION=v1.4.1
|
||||||
|
|
||||||
# GOTESTSUM_VERSION sets the version of gotestsum to install in the dev container.
|
# 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.
|
# 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.
|
# 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
|
# It must be a tag in the docker.io/docker/buildx-bin image repository
|
||||||
@@ -31,7 +31,7 @@ FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
|||||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build-base-alpine
|
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build-base-alpine
|
||||||
ENV GOTOOLCHAIN=local
|
ENV GOTOOLCHAIN=local
|
||||||
COPY --link --from=xx / /
|
COPY --link --from=xx / /
|
||||||
RUN apk add --no-cache bash clang lld llvm file git
|
RUN apk add --no-cache bash clang lld llvm file git git-daemon
|
||||||
WORKDIR /go/src/github.com/docker/cli
|
WORKDIR /go/src/github.com/docker/cli
|
||||||
|
|
||||||
FROM build-base-alpine AS build-alpine
|
FROM build-base-alpine AS build-alpine
|
||||||
|
|||||||
@@ -6,25 +6,35 @@ const (
|
|||||||
// CommandAnnotationPlugin is added to every stub command added by
|
// CommandAnnotationPlugin is added to every stub command added by
|
||||||
// AddPluginCommandStubs with the value "true" and so can be
|
// AddPluginCommandStubs with the value "true" and so can be
|
||||||
// used to distinguish plugin stubs from regular commands.
|
// used to distinguish plugin stubs from regular commands.
|
||||||
|
//
|
||||||
|
// Deprecated: use [metadata.CommandAnnotationPlugin]. This alias will be removed in the next release.
|
||||||
CommandAnnotationPlugin = metadata.CommandAnnotationPlugin
|
CommandAnnotationPlugin = metadata.CommandAnnotationPlugin
|
||||||
|
|
||||||
// CommandAnnotationPluginVendor is added to every stub command
|
// CommandAnnotationPluginVendor is added to every stub command
|
||||||
// added by AddPluginCommandStubs and contains the vendor of
|
// added by AddPluginCommandStubs and contains the vendor of
|
||||||
// that plugin.
|
// that plugin.
|
||||||
|
//
|
||||||
|
// Deprecated: use [metadata.CommandAnnotationPluginVendor]. This alias will be removed in the next release.
|
||||||
CommandAnnotationPluginVendor = metadata.CommandAnnotationPluginVendor
|
CommandAnnotationPluginVendor = metadata.CommandAnnotationPluginVendor
|
||||||
|
|
||||||
// CommandAnnotationPluginVersion is added to every stub command
|
// CommandAnnotationPluginVersion is added to every stub command
|
||||||
// added by AddPluginCommandStubs and contains the version of
|
// added by AddPluginCommandStubs and contains the version of
|
||||||
// that plugin.
|
// that plugin.
|
||||||
|
//
|
||||||
|
// Deprecated: use [metadata.CommandAnnotationPluginVersion]. This alias will be removed in the next release.
|
||||||
CommandAnnotationPluginVersion = metadata.CommandAnnotationPluginVersion
|
CommandAnnotationPluginVersion = metadata.CommandAnnotationPluginVersion
|
||||||
|
|
||||||
// CommandAnnotationPluginInvalid is added to any stub command
|
// CommandAnnotationPluginInvalid is added to any stub command
|
||||||
// added by AddPluginCommandStubs for an invalid command (that
|
// added by AddPluginCommandStubs for an invalid command (that
|
||||||
// is, one which failed it's candidate test) and contains the
|
// is, one which failed it's candidate test) and contains the
|
||||||
// reason for the failure.
|
// reason for the failure.
|
||||||
|
//
|
||||||
|
// Deprecated: use [metadata.CommandAnnotationPluginInvalid]. This alias will be removed in the next release.
|
||||||
CommandAnnotationPluginInvalid = metadata.CommandAnnotationPluginInvalid
|
CommandAnnotationPluginInvalid = metadata.CommandAnnotationPluginInvalid
|
||||||
|
|
||||||
// CommandAnnotationPluginCommandPath is added to overwrite the
|
// CommandAnnotationPluginCommandPath is added to overwrite the
|
||||||
// command path for a plugin invocation.
|
// command path for a plugin invocation.
|
||||||
|
//
|
||||||
|
// Deprecated: use [metadata.CommandAnnotationPluginCommandPath]. This alias will be removed in the next release.
|
||||||
CommandAnnotationPluginCommandPath = metadata.CommandAnnotationPluginCommandPath
|
CommandAnnotationPluginCommandPath = metadata.CommandAnnotationPluginCommandPath
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,12 +6,6 @@ import (
|
|||||||
"github.com/docker/cli/cli-plugins/metadata"
|
"github.com/docker/cli/cli-plugins/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Candidate represents a possible plugin candidate, for mocking purposes
|
|
||||||
type Candidate interface {
|
|
||||||
Path() string
|
|
||||||
Metadata() ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type candidate struct {
|
type candidate struct {
|
||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,14 +32,12 @@ func (c *fakeCandidate) Metadata() ([]byte, error) {
|
|||||||
func TestValidateCandidate(t *testing.T) {
|
func TestValidateCandidate(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
goodPluginName = metadata.NamePrefix + "goodplugin"
|
goodPluginName = metadata.NamePrefix + "goodplugin"
|
||||||
|
builtinName = metadata.NamePrefix + "builtin"
|
||||||
|
builtinAlias = metadata.NamePrefix + "alias"
|
||||||
|
|
||||||
builtinName = metadata.NamePrefix + "builtin"
|
badPrefixPath = "/usr/local/libexec/cli-plugins/wobble"
|
||||||
builtinAlias = metadata.NamePrefix + "alias"
|
badNamePath = "/usr/local/libexec/cli-plugins/docker-123456"
|
||||||
|
goodPluginPath = "/usr/local/libexec/cli-plugins/" + goodPluginName
|
||||||
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}`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fakeroot := &cobra.Command{Use: "docker"}
|
fakeroot := &cobra.Command{Use: "docker"}
|
||||||
@@ -51,31 +49,103 @@ func TestValidateCandidate(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
c *fakeCandidate
|
plugin *fakeCandidate
|
||||||
|
|
||||||
// Either err or invalid may be non-empty, but not both (both can be empty for a good plugin).
|
// Either err or invalid may be non-empty, but not both (both can be empty for a good plugin).
|
||||||
err string
|
err string
|
||||||
invalid string
|
invalid string
|
||||||
|
expVer string
|
||||||
}{
|
}{
|
||||||
/* Each failing one of the tests */
|
// Invalid cases.
|
||||||
{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: "empty path",
|
||||||
{name: "bad path", c: &fakeCandidate{path: badNamePath}, invalid: "did not match"},
|
plugin: &fakeCandidate{path: ""},
|
||||||
{name: "builtin command", c: &fakeCandidate{path: builtinName}, invalid: `plugin "builtin" duplicates builtin command`},
|
err: "plugin candidate path cannot be empty",
|
||||||
{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: "bad prefix",
|
||||||
{name: "empty schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`}, invalid: `plugin SchemaVersion "" is not valid`},
|
plugin: &fakeCandidate{path: badPrefixPath},
|
||||||
{name: "invalid schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`}, invalid: `plugin SchemaVersion "xyzzy" is not valid`},
|
err: fmt.Sprintf("does not have %q prefix", metadata.NamePrefix),
|
||||||
{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: "bad path",
|
||||||
{name: "valid", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`}},
|
plugin: &fakeCandidate{path: badNamePath},
|
||||||
{name: "experimental + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental}},
|
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) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
p, err := newPlugin(tc.c, fakeroot.Commands())
|
p, err := newPlugin(tc.plugin, fakeroot.Commands())
|
||||||
switch {
|
switch {
|
||||||
case tc.err != "":
|
case tc.err != "":
|
||||||
assert.ErrorContains(t, err, tc.err)
|
assert.ErrorContains(t, err, tc.err)
|
||||||
@@ -86,7 +156,7 @@ func TestValidateCandidate(t *testing.T) {
|
|||||||
default:
|
default:
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Equal(t, metadata.NamePrefix+p.Name, goodPluginName)
|
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")
|
assert.Equal(t, p.Vendor, "e2e-testing")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
|
|||||||
rootCmd.AddCommand(&cobra.Command{
|
rootCmd.AddCommand(&cobra.Command{
|
||||||
Use: p.Name,
|
Use: p.Name,
|
||||||
Short: p.ShortDescription,
|
Short: p.ShortDescription,
|
||||||
|
Hidden: p.Hidden,
|
||||||
Run: func(_ *cobra.Command, _ []string) {},
|
Run: func(_ *cobra.Command, _ []string) {},
|
||||||
Annotations: annotations,
|
Annotations: annotations,
|
||||||
DisableFlagParsing: true,
|
DisableFlagParsing: true,
|
||||||
|
|||||||
@@ -23,11 +23,6 @@ func (e *pluginError) Error() string {
|
|||||||
return e.cause.Error()
|
return e.cause.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cause satisfies the errors.causer interface for pluginError.
|
|
||||||
func (e *pluginError) Cause() error {
|
|
||||||
return e.cause
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unwrap provides compatibility for Go 1.13 error chains.
|
// Unwrap provides compatibility for Go 1.13 error chains.
|
||||||
func (e *pluginError) Unwrap() error {
|
func (e *pluginError) Unwrap() error {
|
||||||
return e.cause
|
return e.cause
|
||||||
@@ -41,14 +36,11 @@ func (e *pluginError) MarshalText() (text []byte, err error) {
|
|||||||
// wrapAsPluginError wraps an error in a pluginError with an
|
// wrapAsPluginError wraps an error in a pluginError with an
|
||||||
// additional message.
|
// additional message.
|
||||||
func wrapAsPluginError(err error, msg string) error {
|
func wrapAsPluginError(err error, msg string) error {
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &pluginError{cause: fmt.Errorf("%s: %w", msg, err)}
|
return &pluginError{cause: fmt.Errorf("%s: %w", msg, err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPluginError creates a new pluginError, analogous to
|
// newPluginError creates a new pluginError, analogous to
|
||||||
// errors.Errorf.
|
// errors.Errorf.
|
||||||
func NewPluginError(msg string, args ...any) error {
|
func newPluginError(msg string, args ...any) error {
|
||||||
return &pluginError{cause: fmt.Errorf(msg, args...)}
|
return &pluginError{cause: fmt.Errorf(msg, args...)}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestPluginError(t *testing.T) {
|
func TestPluginError(t *testing.T) {
|
||||||
err := NewPluginError("new error")
|
err := newPluginError("new error")
|
||||||
assert.Check(t, is.Error(err, "new error"))
|
assert.Check(t, is.Error(err, "new error"))
|
||||||
|
|
||||||
inner := errors.New("testing")
|
inner := errors.New("testing")
|
||||||
@@ -21,4 +21,7 @@ func TestPluginError(t *testing.T) {
|
|||||||
actual, err := json.Marshal(err)
|
actual, err := json.Marshal(err)
|
||||||
assert.Check(t, err)
|
assert.Check(t, err)
|
||||||
assert.Check(t, is.Equal(`"wrapping: testing"`, string(actual)))
|
assert.Check(t, is.Equal(`"wrapping: testing"`, string(actual)))
|
||||||
|
|
||||||
|
err = wrapAsPluginError(nil, "wrapping")
|
||||||
|
assert.Check(t, is.Error(err, "wrapping: %!w(<nil>)"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package manager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -9,9 +10,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/containerd/errdefs"
|
||||||
"github.com/docker/cli/cli-plugins/metadata"
|
"github.com/docker/cli/cli-plugins/metadata"
|
||||||
"github.com/docker/cli/cli/config"
|
"github.com/docker/cli/cli/config"
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
|
"github.com/docker/cli/cli/debug"
|
||||||
"github.com/fvbommel/sortorder"
|
"github.com/fvbommel/sortorder"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
@@ -22,13 +25,9 @@ const (
|
|||||||
// used to originally invoke the docker CLI when executing a
|
// used to originally invoke the docker CLI when executing a
|
||||||
// plugin. Assuming $PATH and $CWD remain unchanged this should allow
|
// plugin. Assuming $PATH and $CWD remain unchanged this should allow
|
||||||
// the plugin to re-execute the original CLI.
|
// the plugin to re-execute the original CLI.
|
||||||
ReexecEnvvar = metadata.ReexecEnvvar
|
|
||||||
|
|
||||||
// ResourceAttributesEnvvar is the name of the envvar that includes additional
|
|
||||||
// resource attributes for OTEL.
|
|
||||||
//
|
//
|
||||||
// Deprecated: The "OTEL_RESOURCE_ATTRIBUTES" env-var is part of the OpenTelemetry specification; users should define their own const for this. This const will be removed in the next release.
|
// Deprecated: use [metadata.ReexecEnvvar]. This alias will be removed in the next release.
|
||||||
ResourceAttributesEnvvar = "OTEL_RESOURCE_ATTRIBUTES"
|
ReexecEnvvar = metadata.ReexecEnvvar
|
||||||
)
|
)
|
||||||
|
|
||||||
// errPluginNotFound is the error returned when a plugin could not be found.
|
// errPluginNotFound is the error returned when a plugin could not be found.
|
||||||
@@ -40,15 +39,11 @@ func (e errPluginNotFound) Error() string {
|
|||||||
return "Error: No such CLI plugin: " + string(e)
|
return "Error: No such CLI plugin: " + string(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
type notFound interface{ NotFound() }
|
|
||||||
|
|
||||||
// IsNotFound is true if the given error is due to a plugin not being found.
|
// IsNotFound is true if the given error is due to a plugin not being found.
|
||||||
|
//
|
||||||
|
// Deprecated: use [errdefs.IsNotFound].
|
||||||
func IsNotFound(err error) bool {
|
func IsNotFound(err error) bool {
|
||||||
if e, ok := err.(*pluginError); ok {
|
return errdefs.IsNotFound(err)
|
||||||
err = e.Cause()
|
|
||||||
}
|
|
||||||
_, ok := err.(notFound)
|
|
||||||
return ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPluginDirs returns the platform-specific locations to search for plugins
|
// getPluginDirs returns the platform-specific locations to search for plugins
|
||||||
@@ -81,9 +76,17 @@ func addPluginCandidatesFromDir(res map[string][]string, d string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, dentry := range dentries {
|
for _, dentry := range dentries {
|
||||||
switch dentry.Type() & os.ModeType { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list
|
switch mode := dentry.Type() & os.ModeType; mode { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list
|
||||||
case 0, os.ModeSymlink:
|
case os.ModeSymlink:
|
||||||
// Regular file or symlink, keep going
|
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:
|
default:
|
||||||
// Something else, ignore.
|
// Something else, ignore.
|
||||||
continue
|
continue
|
||||||
@@ -127,7 +130,7 @@ func getPlugin(name string, pluginDirs []string, rootcmd *cobra.Command) (*Plugi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !IsNotFound(p.Err) {
|
if !errdefs.IsNotFound(p.Err) {
|
||||||
p.ShadowedPaths = paths[1:]
|
p.ShadowedPaths = paths[1:]
|
||||||
}
|
}
|
||||||
return &p, nil
|
return &p, nil
|
||||||
@@ -164,7 +167,7 @@ func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !IsNotFound(p.Err) {
|
if !errdefs.IsNotFound(p.Err) {
|
||||||
p.ShadowedPaths = paths[1:]
|
p.ShadowedPaths = paths[1:]
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
@@ -185,9 +188,9 @@ func ListPlugins(dockerCli config.Provider, rootcmd *cobra.Command) ([]Plugin, e
|
|||||||
return plugins, nil
|
return plugins, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginRunCommand returns an "os/exec".Cmd which when .Run() will execute the named plugin.
|
// PluginRunCommand returns an [os/exec.Cmd] which when [os/exec.Cmd.Run] will execute the named plugin.
|
||||||
// The rootcmd argument is referenced to determine the set of builtin commands in order to detect conficts.
|
// The rootcmd argument is referenced to determine the set of builtin commands in order to detect conficts.
|
||||||
// The error returned satisfies the IsNotFound() predicate if no plugin was found or if the first candidate plugin was invalid somehow.
|
// The error returned satisfies the [errdefs.IsNotFound] predicate if no plugin was found or if the first candidate plugin was invalid somehow.
|
||||||
func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Command) (*exec.Cmd, error) {
|
func PluginRunCommand(dockerCli config.Provider, name string, rootcmd *cobra.Command) (*exec.Cmd, error) {
|
||||||
// This uses the full original args, not the args which may
|
// This uses the full original args, not the args which may
|
||||||
// have been provided by cobra to our caller. This is because
|
// have been provided by cobra to our caller. This is because
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containerd/errdefs"
|
||||||
"github.com/docker/cli/cli/config"
|
"github.com/docker/cli/cli/config"
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
@@ -37,7 +38,7 @@ func TestListPluginCandidates(t *testing.T) {
|
|||||||
"plugins3-target", // Will be referenced as a symlink from below
|
"plugins3-target", // Will be referenced as a symlink from below
|
||||||
fs.WithFile("docker-plugin1", ""),
|
fs.WithFile("docker-plugin1", ""),
|
||||||
fs.WithDir("ignored3"),
|
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.WithFile("non-plugin-symlinked", ""), // This shouldn't appear, but ...
|
||||||
fs.WithSymlink("docker-symlinked", "non-plugin-symlinked"), // ... this link to it should.
|
fs.WithSymlink("docker-symlinked", "non-plugin-symlinked"), // ... this link to it should.
|
||||||
),
|
),
|
||||||
@@ -71,9 +72,6 @@ func TestListPluginCandidates(t *testing.T) {
|
|||||||
"hardlink2": {
|
"hardlink2": {
|
||||||
dir.Join("plugins2", "docker-hardlink2"),
|
dir.Join("plugins2", "docker-hardlink2"),
|
||||||
},
|
},
|
||||||
"brokensymlink": {
|
|
||||||
dir.Join("plugins3", "docker-brokensymlink"),
|
|
||||||
},
|
|
||||||
"symlinked": {
|
"symlinked": {
|
||||||
dir.Join("plugins3", "docker-symlinked"),
|
dir.Join("plugins3", "docker-symlinked"),
|
||||||
},
|
},
|
||||||
@@ -131,7 +129,7 @@ echo '{"SchemaVersion":"0.1.0"}'`, fs.WithMode(0o777)),
|
|||||||
|
|
||||||
_, err = GetPlugin("ccc", cli, &cobra.Command{})
|
_, err = GetPlugin("ccc", cli, &cobra.Command{})
|
||||||
assert.Error(t, err, "Error: No such CLI plugin: ccc")
|
assert.Error(t, err, "Error: No such CLI plugin: ccc")
|
||||||
assert.Assert(t, IsNotFound(err))
|
assert.Assert(t, errdefs.IsNotFound(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListPluginsIsSorted(t *testing.T) {
|
func TestListPluginsIsSorted(t *testing.T) {
|
||||||
@@ -166,8 +164,8 @@ func TestErrPluginNotFound(t *testing.T) {
|
|||||||
var err error = errPluginNotFound("test")
|
var err error = errPluginNotFound("test")
|
||||||
err.(errPluginNotFound).NotFound()
|
err.(errPluginNotFound).NotFound()
|
||||||
assert.Error(t, err, "Error: No such CLI plugin: test")
|
assert.Error(t, err, "Error: No such CLI plugin: test")
|
||||||
assert.Assert(t, IsNotFound(err))
|
assert.Assert(t, errdefs.IsNotFound(err))
|
||||||
assert.Assert(t, !IsNotFound(nil))
|
assert.Assert(t, !errdefs.IsNotFound(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetPluginDirs(t *testing.T) {
|
func TestGetPluginDirs(t *testing.T) {
|
||||||
|
|||||||
@@ -6,18 +6,26 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// NamePrefix is the prefix required on all plugin binary names
|
// NamePrefix is the prefix required on all plugin binary names
|
||||||
|
//
|
||||||
|
// Deprecated: use [metadata.NamePrefix]. This alias will be removed in a future release.
|
||||||
NamePrefix = metadata.NamePrefix
|
NamePrefix = metadata.NamePrefix
|
||||||
|
|
||||||
// MetadataSubcommandName is the name of the plugin subcommand
|
// MetadataSubcommandName is the name of the plugin subcommand
|
||||||
// which must be supported by every plugin and returns the
|
// which must be supported by every plugin and returns the
|
||||||
// plugin metadata.
|
// plugin metadata.
|
||||||
|
//
|
||||||
|
// Deprecated: use [metadata.MetadataSubcommandName]. This alias will be removed in a future release.
|
||||||
MetadataSubcommandName = metadata.MetadataSubcommandName
|
MetadataSubcommandName = metadata.MetadataSubcommandName
|
||||||
|
|
||||||
// HookSubcommandName is the name of the plugin subcommand
|
// HookSubcommandName is the name of the plugin subcommand
|
||||||
// which must be implemented by plugins declaring support
|
// which must be implemented by plugins declaring support
|
||||||
// for hooks in their metadata.
|
// for hooks in their metadata.
|
||||||
|
//
|
||||||
|
// Deprecated: use [metadata.HookSubcommandName]. This alias will be removed in a future release.
|
||||||
HookSubcommandName = metadata.HookSubcommandName
|
HookSubcommandName = metadata.HookSubcommandName
|
||||||
)
|
)
|
||||||
|
|
||||||
// Metadata provided by the plugin.
|
// Metadata provided by the plugin.
|
||||||
|
//
|
||||||
|
// Deprecated: use [metadata.Metadata]. This alias will be removed in a future release.
|
||||||
type Metadata = metadata.Metadata
|
type Metadata = metadata.Metadata
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package manager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli-plugins/metadata"
|
"github.com/docker/cli/cli-plugins/metadata"
|
||||||
@@ -31,12 +33,34 @@ type Plugin struct {
|
|||||||
ShadowedPaths []string `json:",omitempty"`
|
ShadowedPaths []string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements [json.Marshaler] to handle marshaling the
|
||||||
|
// [Plugin.Err] field (Go doesn't marshal errors by default).
|
||||||
|
func (p *Plugin) MarshalJSON() ([]byte, error) {
|
||||||
|
type Alias Plugin // avoid recursion
|
||||||
|
|
||||||
|
cp := *p // shallow copy to avoid mutating original
|
||||||
|
|
||||||
|
if cp.Err != nil {
|
||||||
|
if _, ok := cp.Err.(encoding.TextMarshaler); !ok {
|
||||||
|
cp.Err = &pluginError{cp.Err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal((*Alias)(&cp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// pluginCandidate represents a possible plugin candidate, for mocking purposes.
|
||||||
|
type pluginCandidate interface {
|
||||||
|
Path() string
|
||||||
|
Metadata() ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
// newPlugin determines if the given candidate is valid and returns a
|
// newPlugin determines if the given candidate is valid and returns a
|
||||||
// Plugin. If the candidate fails one of the tests then `Plugin.Err`
|
// Plugin. If the candidate fails one of the tests then `Plugin.Err`
|
||||||
// is set, and is always a `pluginError`, but the `Plugin` is still
|
// is set, and is always a `pluginError`, but the `Plugin` is still
|
||||||
// returned with no error. An error is only returned due to a
|
// returned with no error. An error is only returned due to a
|
||||||
// non-recoverable error.
|
// non-recoverable error.
|
||||||
func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
|
func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
|
||||||
path := c.Path()
|
path := c.Path()
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return Plugin{}, errors.New("plugin candidate path cannot be empty")
|
return Plugin{}, errors.New("plugin candidate path cannot be empty")
|
||||||
@@ -63,7 +87,7 @@ func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
|
|||||||
|
|
||||||
// Now apply the candidate tests, so these update p.Err.
|
// Now apply the candidate tests, so these update p.Err.
|
||||||
if !pluginNameRe.MatchString(p.Name) {
|
if !pluginNameRe.MatchString(p.Name) {
|
||||||
p.Err = NewPluginError("plugin candidate %q did not match %q", p.Name, pluginNameRe.String())
|
p.Err = newPluginError("plugin candidate %q did not match %q", p.Name, pluginNameRe.String())
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,11 +99,11 @@ func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if cmd.Name() == p.Name {
|
if cmd.Name() == p.Name {
|
||||||
p.Err = NewPluginError("plugin %q duplicates builtin command", p.Name)
|
p.Err = newPluginError("plugin %q duplicates builtin command", p.Name)
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
if cmd.HasAlias(p.Name) {
|
if cmd.HasAlias(p.Name) {
|
||||||
p.Err = NewPluginError("plugin %q duplicates an alias of builtin command %q", p.Name, cmd.Name())
|
p.Err = newPluginError("plugin %q duplicates an alias of builtin command %q", p.Name, cmd.Name())
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,17 +119,42 @@ func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
|
|||||||
p.Err = wrapAsPluginError(err, "invalid metadata")
|
p.Err = wrapAsPluginError(err, "invalid metadata")
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
if p.Metadata.SchemaVersion != "0.1.0" {
|
if err := validateSchemaVersion(p.Metadata.SchemaVersion); err != nil {
|
||||||
p.Err = NewPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
|
p.Err = &pluginError{cause: err}
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
if p.Metadata.Vendor == "" {
|
if p.Metadata.Vendor == "" {
|
||||||
p.Err = NewPluginError("plugin metadata does not define a vendor")
|
p.Err = newPluginError("plugin metadata does not define a vendor")
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
return p, nil
|
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
|
// RunHook executes the plugin's hooks command
|
||||||
// and returns its unprocessed output.
|
// and returns its unprocessed output.
|
||||||
func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte, error) {
|
func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte, error) {
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
is "gotest.tools/v3/assert/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPluginMarshal(t *testing.T) {
|
||||||
|
const jsonWithError = `{"Name":"some-plugin","Err":"something went wrong"}`
|
||||||
|
const jsonNoError = `{"Name":"some-plugin"}`
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
doc string
|
||||||
|
error error
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
doc: "no error",
|
||||||
|
expected: jsonNoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "regular error",
|
||||||
|
error: errors.New("something went wrong"),
|
||||||
|
expected: jsonWithError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "custom error",
|
||||||
|
error: newPluginError("something went wrong"),
|
||||||
|
expected: jsonWithError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
|
actual, err := json.Marshal(&Plugin{Name: "some-plugin", Err: tc.error})
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Check(t, is.Equal(string(actual), tc.expected))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,4 +33,6 @@ type Metadata struct {
|
|||||||
ShortDescription string `json:",omitempty"`
|
ShortDescription string `json:",omitempty"`
|
||||||
// URL is a pointer to the plugin's homepage.
|
// URL is a pointer to the plugin's homepage.
|
||||||
URL string `json:",omitempty"`
|
URL string `json:",omitempty"`
|
||||||
|
// Hidden hides the plugin in completion and help message output.
|
||||||
|
Hidden bool `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,19 +80,23 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadat
|
|||||||
return cmd.Execute()
|
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.
|
// Run is the top-level entry point to the CLI plugin framework. It should
|
||||||
func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) {
|
// 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)
|
otel.SetErrorHandler(debug.OTELErrorHandler)
|
||||||
|
|
||||||
dockerCli, err := command.NewDockerCli()
|
dockerCLI, err := command.NewDockerCli(ops...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
_, _ = fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
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
|
var stErr cli.StatusError
|
||||||
if errors.As(err, &stErr) {
|
if errors.As(err, &stErr) {
|
||||||
// StatusError should only be used for errors, and all errors should
|
// 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.
|
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
|
stErr.StatusCode = 1
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintln(dockerCli.Err(), stErr)
|
_, _ = fmt.Fprintln(dockerCLI.Err(), stErr)
|
||||||
os.Exit(stErr.StatusCode)
|
os.Exit(stErr.StatusCode)
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintln(dockerCli.Err(), err)
|
_, _ = fmt.Fprintln(dockerCLI.Err(), err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,11 +179,24 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta
|
|||||||
newMetadataSubcommand(plugin, meta),
|
newMetadataSubcommand(plugin, meta),
|
||||||
)
|
)
|
||||||
|
|
||||||
cli.DisableFlagsInUseLine(cmd)
|
visitAll(cmd,
|
||||||
|
// prevent adding "[flags]" to the end of the usage line.
|
||||||
|
func(c *cobra.Command) { c.DisableFlagsInUseLine = true },
|
||||||
|
)
|
||||||
|
|
||||||
return cli.NewTopLevelCommand(cmd, dockerCli, opts, cmd.Flags())
|
return cli.NewTopLevelCommand(cmd, dockerCli, opts, cmd.Flags())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// visitAll traverses all commands from the root.
|
||||||
|
func visitAll(root *cobra.Command, fns ...func(*cobra.Command)) {
|
||||||
|
for _, cmd := range root.Commands() {
|
||||||
|
visitAll(cmd, fns...)
|
||||||
|
}
|
||||||
|
for _, fn := range fns {
|
||||||
|
fn(root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newMetadataSubcommand(plugin *cobra.Command, meta metadata.Metadata) *cobra.Command {
|
func newMetadataSubcommand(plugin *cobra.Command, meta metadata.Metadata) *cobra.Command {
|
||||||
if meta.ShortDescription == "" {
|
if meta.ShortDescription == "" {
|
||||||
meta.ShortDescription = plugin.Short
|
meta.ShortDescription = plugin.Short
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVisitAll(t *testing.T) {
|
||||||
|
root := &cobra.Command{Use: "root"}
|
||||||
|
sub1 := &cobra.Command{Use: "sub1"}
|
||||||
|
sub1sub1 := &cobra.Command{Use: "sub1sub1"}
|
||||||
|
sub1sub2 := &cobra.Command{Use: "sub1sub2"}
|
||||||
|
sub2 := &cobra.Command{Use: "sub2"}
|
||||||
|
|
||||||
|
root.AddCommand(sub1, sub2)
|
||||||
|
sub1.AddCommand(sub1sub1, sub1sub2)
|
||||||
|
|
||||||
|
var visited []string
|
||||||
|
visitAll(root, func(ccmd *cobra.Command) {
|
||||||
|
visited = append(visited, ccmd.Name())
|
||||||
|
})
|
||||||
|
expected := []string{"sub1sub1", "sub1sub2", "sub1", "sub2", "root"}
|
||||||
|
if !slices.Equal(expected, visited) {
|
||||||
|
t.Errorf("expected %#v, got %#v", expected, visited)
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-19
@@ -168,34 +168,30 @@ func (tcmd *TopLevelCommand) Initialize(ops ...command.CLIOption) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// VisitAll will traverse all commands from the root.
|
// VisitAll will traverse all commands from the root.
|
||||||
// This is different from the VisitAll of cobra.Command where only parents
|
//
|
||||||
// are checked.
|
// Deprecated: this utility was only used internally and will be removed in the next release.
|
||||||
func VisitAll(root *cobra.Command, fn func(*cobra.Command)) {
|
func VisitAll(root *cobra.Command, fn func(*cobra.Command)) {
|
||||||
|
visitAll(root, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func visitAll(root *cobra.Command, fn func(*cobra.Command)) {
|
||||||
for _, cmd := range root.Commands() {
|
for _, cmd := range root.Commands() {
|
||||||
VisitAll(cmd, fn)
|
visitAll(cmd, fn)
|
||||||
}
|
}
|
||||||
fn(root)
|
fn(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all
|
// DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all
|
||||||
// commands within the tree rooted at cmd.
|
// commands within the tree rooted at cmd.
|
||||||
|
//
|
||||||
|
// Deprecated: this utility was only used internally and will be removed in the next release.
|
||||||
func DisableFlagsInUseLine(cmd *cobra.Command) {
|
func DisableFlagsInUseLine(cmd *cobra.Command) {
|
||||||
VisitAll(cmd, func(ccmd *cobra.Command) {
|
visitAll(cmd, func(ccmd *cobra.Command) {
|
||||||
// do not add a `[flags]` to the end of the usage line.
|
// do not add a `[flags]` to the end of the usage line.
|
||||||
ccmd.DisableFlagsInUseLine = true
|
ccmd.DisableFlagsInUseLine = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasCompletionArg returns true if a cobra completion arg request is found.
|
|
||||||
func HasCompletionArg(args []string) bool {
|
|
||||||
for _, arg := range args {
|
|
||||||
if arg == cobra.ShellCompRequestCmd || arg == cobra.ShellCompNoDescRequestCmd {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var helpCommand = &cobra.Command{
|
var helpCommand = &cobra.Command{
|
||||||
Use: "help [command]",
|
Use: "help [command]",
|
||||||
Short: "Help about the command",
|
Short: "Help about the command",
|
||||||
@@ -380,13 +376,10 @@ func orchestratorSubCommands(cmd *cobra.Command) []*cobra.Command {
|
|||||||
func allManagementSubCommands(cmd *cobra.Command) []*cobra.Command {
|
func allManagementSubCommands(cmd *cobra.Command) []*cobra.Command {
|
||||||
cmds := []*cobra.Command{}
|
cmds := []*cobra.Command{}
|
||||||
for _, sub := range cmd.Commands() {
|
for _, sub := range cmd.Commands() {
|
||||||
if isPlugin(sub) {
|
if invalidPluginReason(sub) != "" {
|
||||||
if invalidPluginReason(sub) == "" {
|
|
||||||
cmds = append(cmds, sub)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if sub.IsAvailableCommand() && sub.HasSubCommands() {
|
if sub.IsAvailableCommand() && (isPlugin(sub) || sub.HasSubCommands()) {
|
||||||
cmds = append(cmds, sub)
|
cmds = append(cmds, sub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-22
@@ -10,28 +10,6 @@ import (
|
|||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestVisitAll(t *testing.T) {
|
|
||||||
root := &cobra.Command{Use: "root"}
|
|
||||||
sub1 := &cobra.Command{Use: "sub1"}
|
|
||||||
sub1sub1 := &cobra.Command{Use: "sub1sub1"}
|
|
||||||
sub1sub2 := &cobra.Command{Use: "sub1sub2"}
|
|
||||||
sub2 := &cobra.Command{Use: "sub2"}
|
|
||||||
|
|
||||||
root.AddCommand(sub1, sub2)
|
|
||||||
sub1.AddCommand(sub1sub1, sub1sub2)
|
|
||||||
|
|
||||||
// Take the opportunity to test DisableFlagsInUseLine too
|
|
||||||
DisableFlagsInUseLine(root)
|
|
||||||
|
|
||||||
var visited []string
|
|
||||||
VisitAll(root, func(ccmd *cobra.Command) {
|
|
||||||
visited = append(visited, ccmd.Name())
|
|
||||||
assert.Assert(t, ccmd.DisableFlagsInUseLine, "DisableFlagsInUseLine not set on %q", ccmd.Name())
|
|
||||||
})
|
|
||||||
expected := []string{"sub1sub1", "sub1sub2", "sub1", "sub2", "root"}
|
|
||||||
assert.DeepEqual(t, expected, visited)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVendorAndVersion(t *testing.T) {
|
func TestVendorAndVersion(t *testing.T) {
|
||||||
// Non plugin.
|
// Non plugin.
|
||||||
assert.Equal(t, vendorAndVersion(&cobra.Command{Use: "test"}), "")
|
assert.Equal(t, vendorAndVersion(&cobra.Command{Use: "test"}), "")
|
||||||
@@ -78,6 +56,33 @@ func TestInvalidPlugin(t *testing.T) {
|
|||||||
assert.DeepEqual(t, invalidPlugins(root), []*cobra.Command{sub1}, cmpopts.IgnoreUnexported(cobra.Command{}))
|
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) {
|
func TestCommandAliases(t *testing.T) {
|
||||||
root := &cobra.Command{Use: "root"}
|
root := &cobra.Command{Use: "root"}
|
||||||
sub := &cobra.Command{Use: "subcommand", Aliases: []string{"alias1", "alias2"}}
|
sub := &cobra.Command{Use: "subcommand", Aliases: []string{"alias1", "alias2"}}
|
||||||
|
|||||||
@@ -9,17 +9,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewBuilderCommand returns a cobra command for `builder` subcommands
|
// NewBuilderCommand returns a cobra command for `builder` subcommands
|
||||||
func NewBuilderCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewBuilderCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newBuilderCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuilderCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "builder",
|
Use: "builder",
|
||||||
Short: "Manage builds",
|
Short: "Manage builds",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: command.ShowHelp(dockerCli.Err()),
|
RunE: command.ShowHelp(dockerCLI.Err()),
|
||||||
Annotations: map[string]string{"version": "1.31"},
|
Annotations: map[string]string{"version": "1.31"},
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
NewPruneCommand(dockerCli),
|
newPruneCommand(dockerCLI),
|
||||||
image.NewBuildCommand(dockerCli),
|
// we should have a mechanism for registering sub-commands in the cli/internal/commands.Register function.
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
|
image.NewBuildCommand(dockerCLI),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -28,7 +36,13 @@ func NewBuilderCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
// This command is a placeholder / stub that is dynamically replaced by an
|
// This command is a placeholder / stub that is dynamically replaced by an
|
||||||
// alias for "docker buildx bake" if BuildKit is enabled (and the buildx plugin
|
// alias for "docker buildx bake" if BuildKit is enabled (and the buildx plugin
|
||||||
// installed).
|
// installed).
|
||||||
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
func NewBakeStubCommand(dockerCLI command.Streams) *cobra.Command {
|
func NewBakeStubCommand(dockerCLI command.Streams) *cobra.Command {
|
||||||
|
return newBakeStubCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBakeStubCommand(dockerCLI command.Streams) *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "bake [OPTIONS] [TARGET...]",
|
Use: "bake [OPTIONS] [TARGET...]",
|
||||||
Short: "Build from a file",
|
Short: "Build from a file",
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/completion"
|
|
||||||
"github.com/docker/cli/internal/prompt"
|
"github.com/docker/cli/internal/prompt"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/build"
|
"github.com/docker/docker/api/types/build"
|
||||||
@@ -24,7 +23,14 @@ type pruneOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPruneCommand returns a new cobra prune command for images
|
// NewPruneCommand returns a new cobra prune command for images
|
||||||
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
return newPruneCommand(dockerCli)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPruneCommand returns a new cobra prune command for images
|
||||||
|
func newPruneCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
options := pruneOptions{filter: opts.NewFilterOpt()}
|
options := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -32,18 +38,18 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Short: "Remove build cache",
|
Short: "Remove build cache",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCli, options)
|
spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCLI, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if output != "" {
|
if output != "" {
|
||||||
fmt.Fprintln(dockerCli.Out(), output)
|
_, _ = fmt.Fprintln(dockerCLI.Out(), output)
|
||||||
}
|
}
|
||||||
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
|
_, _ = fmt.Fprintln(dockerCLI.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{"version": "1.39"},
|
Annotations: map[string]string{"version": "1.39"},
|
||||||
ValidArgsFunction: completion.NoComplete,
|
ValidArgsFunction: cobra.NoFileCompletions,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
@@ -79,9 +85,12 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
|
|||||||
}
|
}
|
||||||
|
|
||||||
report, err := dockerCli.Client().BuildCachePrune(ctx, build.CachePruneOptions{
|
report, err := dockerCli.Client().BuildCachePrune(ctx, build.CachePruneOptions{
|
||||||
All: options.all,
|
All: options.all,
|
||||||
KeepStorage: options.keepStorage.Value(), // FIXME(thaJeztah): rewrite to use new options; see https://github.com/moby/moby/pull/48720
|
// TODO(austinvazquez): remove when updated to use github.com/moby/moby/client@v0.1.0
|
||||||
Filters: pruneFilters,
|
// See https://github.com/moby/moby/pull/50772 for more details.
|
||||||
|
KeepStorage: options.keepStorage.Value(),
|
||||||
|
ReservedSpace: options.keepStorage.Value(),
|
||||||
|
Filters: pruneFilters,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, "", err
|
return 0, "", err
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestBuilderPromptTermination(t *testing.T) {
|
|||||||
return nil, errors.New("fakeClient builderPruneFunc should not be called")
|
return nil, errors.New("fakeClient builderPruneFunc should not be called")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := NewPruneCommand(cli)
|
cmd := newPruneCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
test.TerminatePrompt(ctx, t, cmd, cli)
|
test.TerminatePrompt(ctx, t, cmd, cli)
|
||||||
|
|||||||
@@ -7,12 +7,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewCheckpointCommand returns the `checkpoint` subcommand (only in experimental)
|
// NewCheckpointCommand returns the `checkpoint` subcommand (only in experimental)
|
||||||
func NewCheckpointCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewCheckpointCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newCheckpointCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCheckpointCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "checkpoint",
|
Use: "checkpoint",
|
||||||
Short: "Manage checkpoints",
|
Short: "Manage checkpoints",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: command.ShowHelp(dockerCli.Err()),
|
RunE: command.ShowHelp(dockerCLI.Err()),
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"experimental": "",
|
"experimental": "",
|
||||||
"ostype": "linux",
|
"ostype": "linux",
|
||||||
@@ -20,9 +26,9 @@ func NewCheckpointCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
newCreateCommand(dockerCli),
|
newCreateCommand(dockerCLI),
|
||||||
newListCommand(dockerCli),
|
newListCommand(dockerCLI),
|
||||||
newRemoveCommand(dockerCli),
|
newRemoveCommand(dockerCLI),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/completion"
|
|
||||||
"github.com/docker/docker/api/types/checkpoint"
|
"github.com/docker/docker/api/types/checkpoint"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -30,7 +29,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
opts.checkpoint = args[1]
|
opts.checkpoint = args[1]
|
||||||
return runCreate(cmd.Context(), dockerCli, opts)
|
return runCreate(cmd.Context(), dockerCli, opts)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.NoComplete,
|
ValidArgsFunction: cobra.NoFileCompletions,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|||||||
@@ -11,7 +11,14 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewFormat returns a format for use with a checkpoint Context
|
// NewFormat returns a format for use with a checkpoint Context
|
||||||
|
//
|
||||||
|
// Deprecated: this function was only used internally and will be removed in the next release.
|
||||||
func NewFormat(source string) formatter.Format {
|
func NewFormat(source string) formatter.Format {
|
||||||
|
return newFormat(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFormat returns a format for use with a checkpointContext.
|
||||||
|
func newFormat(source string) formatter.Format {
|
||||||
if source == formatter.TableFormatKey {
|
if source == formatter.TableFormatKey {
|
||||||
return defaultCheckpointFormat
|
return defaultCheckpointFormat
|
||||||
}
|
}
|
||||||
@@ -19,7 +26,14 @@ func NewFormat(source string) formatter.Format {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FormatWrite writes formatted checkpoints using the Context
|
// FormatWrite writes formatted checkpoints using the Context
|
||||||
func FormatWrite(ctx formatter.Context, checkpoints []checkpoint.Summary) error {
|
//
|
||||||
|
// Deprecated: this function was only used internally and will be removed in the next release.
|
||||||
|
func FormatWrite(fmtCtx formatter.Context, checkpoints []checkpoint.Summary) error {
|
||||||
|
return formatWrite(fmtCtx, checkpoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatWrite writes formatted checkpoints using the Context
|
||||||
|
func formatWrite(fmtCtx formatter.Context, checkpoints []checkpoint.Summary) error {
|
||||||
render := func(format func(subContext formatter.SubContext) error) error {
|
render := func(format func(subContext formatter.SubContext) error) error {
|
||||||
for _, cp := range checkpoints {
|
for _, cp := range checkpoints {
|
||||||
if err := format(&checkpointContext{c: cp}); err != nil {
|
if err := format(&checkpointContext{c: cp}); err != nil {
|
||||||
@@ -28,7 +42,7 @@ func FormatWrite(ctx formatter.Context, checkpoints []checkpoint.Summary) error
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return ctx.Write(newCheckpointContext(), render)
|
return fmtCtx.Write(newCheckpointContext(), render)
|
||||||
}
|
}
|
||||||
|
|
||||||
type checkpointContext struct {
|
type checkpointContext struct {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func TestCheckpointContextFormatWrite(t *testing.T) {
|
|||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
formatter.Context{Format: NewFormat(defaultCheckpointFormat)},
|
formatter.Context{Format: newFormat(defaultCheckpointFormat)},
|
||||||
`CHECKPOINT NAME
|
`CHECKPOINT NAME
|
||||||
checkpoint-1
|
checkpoint-1
|
||||||
checkpoint-2
|
checkpoint-2
|
||||||
@@ -23,14 +23,14 @@ checkpoint-3
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
formatter.Context{Format: NewFormat("{{.Name}}")},
|
formatter.Context{Format: newFormat("{{.Name}}")},
|
||||||
`checkpoint-1
|
`checkpoint-1
|
||||||
checkpoint-2
|
checkpoint-2
|
||||||
checkpoint-3
|
checkpoint-3
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
formatter.Context{Format: NewFormat("{{.Name}}:")},
|
formatter.Context{Format: newFormat("{{.Name}}:")},
|
||||||
`checkpoint-1:
|
`checkpoint-1:
|
||||||
checkpoint-2:
|
checkpoint-2:
|
||||||
checkpoint-3:
|
checkpoint-3:
|
||||||
@@ -41,7 +41,7 @@ checkpoint-3:
|
|||||||
for _, testcase := range cases {
|
for _, testcase := range cases {
|
||||||
out := bytes.NewBufferString("")
|
out := bytes.NewBufferString("")
|
||||||
testcase.context.Output = out
|
testcase.context.Output = out
|
||||||
err := FormatWrite(testcase.context, []checkpoint.Summary{
|
err := formatWrite(testcase.context, []checkpoint.Summary{
|
||||||
{Name: "checkpoint-1"},
|
{Name: "checkpoint-1"},
|
||||||
{Name: "checkpoint-2"},
|
{Name: "checkpoint-2"},
|
||||||
{Name: "checkpoint-3"},
|
{Name: "checkpoint-3"},
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func runList(ctx context.Context, dockerCli command.Cli, container string, opts
|
|||||||
|
|
||||||
cpCtx := formatter.Context{
|
cpCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: NewFormat(formatter.TableFormatKey),
|
Format: newFormat(formatter.TableFormatKey),
|
||||||
}
|
}
|
||||||
return FormatWrite(cpCtx, checkpoints)
|
return formatWrite(cpCtx, checkpoints)
|
||||||
}
|
}
|
||||||
|
|||||||
+90
-12
@@ -48,9 +48,7 @@ type Cli interface {
|
|||||||
Apply(ops ...CLIOption) error
|
Apply(ops ...CLIOption) error
|
||||||
config.Provider
|
config.Provider
|
||||||
ServerInfo() ServerInfo
|
ServerInfo() ServerInfo
|
||||||
DefaultVersion() string
|
|
||||||
CurrentVersion() string
|
CurrentVersion() string
|
||||||
ContentTrustEnabled() bool
|
|
||||||
BuildKitEnabled() (bool, error)
|
BuildKitEnabled() (bool, error)
|
||||||
ContextStore() store.Store
|
ContextStore() store.Store
|
||||||
CurrentContext() string
|
CurrentContext() string
|
||||||
@@ -78,6 +76,7 @@ type DockerCli struct {
|
|||||||
dockerEndpoint docker.Endpoint
|
dockerEndpoint docker.Endpoint
|
||||||
contextStoreConfig *store.Config
|
contextStoreConfig *store.Config
|
||||||
initTimeout time.Duration
|
initTimeout time.Duration
|
||||||
|
userAgent string
|
||||||
res telemetryResource
|
res telemetryResource
|
||||||
|
|
||||||
// baseCtx is the base context used for internal operations. In the future
|
// baseCtx is the base context used for internal operations. In the future
|
||||||
@@ -89,6 +88,8 @@ type DockerCli struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultVersion returns [api.DefaultVersion].
|
// DefaultVersion returns [api.DefaultVersion].
|
||||||
|
//
|
||||||
|
// Deprecated: this function is no longer used and will be removed in the next release.
|
||||||
func (*DockerCli) DefaultVersion() string {
|
func (*DockerCli) DefaultVersion() string {
|
||||||
return api.DefaultVersion
|
return api.DefaultVersion
|
||||||
}
|
}
|
||||||
@@ -159,6 +160,8 @@ func (cli *DockerCli) ServerInfo() ServerInfo {
|
|||||||
|
|
||||||
// ContentTrustEnabled returns whether content trust has been enabled by an
|
// ContentTrustEnabled returns whether content trust has been enabled by an
|
||||||
// environment variable.
|
// 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 {
|
func (cli *DockerCli) ContentTrustEnabled() bool {
|
||||||
return cli.contentTrust
|
return cli.contentTrust
|
||||||
}
|
}
|
||||||
@@ -269,7 +272,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
|||||||
cli.contextStore = &ContextStoreWithDefault{
|
cli.contextStore = &ContextStoreWithDefault{
|
||||||
Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig),
|
Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig),
|
||||||
Resolver: func() (*DefaultContext, error) {
|
Resolver: func() (*DefaultContext, error) {
|
||||||
return ResolveDefaultContext(cli.options, *cli.contextStoreConfig)
|
return resolveDefaultContext(cli.options, *cli.contextStoreConfig)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,6 +285,17 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
|||||||
}
|
}
|
||||||
filterResourceAttributesEnvvar()
|
filterResourceAttributesEnvvar()
|
||||||
|
|
||||||
|
// early return if GODEBUG is already set or the docker context is
|
||||||
|
// the default context, i.e. is a virtual context where we won't override
|
||||||
|
// any GODEBUG values.
|
||||||
|
if v := os.Getenv("GODEBUG"); cli.currentContext == DefaultContextName || v != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
meta, err := cli.contextStore.GetMetadata(cli.currentContext)
|
||||||
|
if err == nil {
|
||||||
|
setGoDebug(meta)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,17 +309,17 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.
|
|||||||
contextStore := &ContextStoreWithDefault{
|
contextStore := &ContextStoreWithDefault{
|
||||||
Store: store.New(config.ContextStoreDir(), storeConfig),
|
Store: store.New(config.ContextStoreDir(), storeConfig),
|
||||||
Resolver: func() (*DefaultContext, error) {
|
Resolver: func() (*DefaultContext, error) {
|
||||||
return ResolveDefaultContext(opts, storeConfig)
|
return resolveDefaultContext(opts, storeConfig)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
endpoint, err := resolveDockerEndpoint(contextStore, resolveContextName(opts, configFile))
|
endpoint, err := resolveDockerEndpoint(contextStore, resolveContextName(opts, configFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "unable to resolve docker endpoint")
|
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()
|
opts, err := ep.ClientOpts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -313,7 +327,14 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF
|
|||||||
if len(configFile.HTTPHeaders) > 0 {
|
if len(configFile.HTTPHeaders) > 0 {
|
||||||
opts = append(opts, client.WithHTTPHeaders(configFile.HTTPHeaders))
|
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...)
|
return client.NewClientWithOpts(opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,6 +496,57 @@ func (cli *DockerCli) getDockerEndPoint() (ep docker.Endpoint, err error) {
|
|||||||
return resolveDockerEndpoint(cli.contextStore, cn)
|
return resolveDockerEndpoint(cli.contextStore, cn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setGoDebug is an escape hatch that sets the GODEBUG environment
|
||||||
|
// variable value using docker context metadata.
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "Name": "my-context",
|
||||||
|
// "Metadata": { "GODEBUG": "x509negativeserial=1" }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// WARNING: Setting x509negativeserial=1 allows Go's x509 library to accept
|
||||||
|
// X.509 certificates with negative serial numbers.
|
||||||
|
// This behavior is deprecated and non-compliant with current security
|
||||||
|
// standards (RFC 5280). Accepting negative serial numbers can introduce
|
||||||
|
// serious security vulnerabilities, including the risk of certificate
|
||||||
|
// collision or bypass attacks.
|
||||||
|
// This option should only be used for legacy compatibility and never in
|
||||||
|
// production environments.
|
||||||
|
// Use at your own risk.
|
||||||
|
func setGoDebug(meta store.Metadata) {
|
||||||
|
fieldName := "GODEBUG"
|
||||||
|
godebugEnv := os.Getenv(fieldName)
|
||||||
|
// early return if GODEBUG is already set. We don't want to override what
|
||||||
|
// the user already sets.
|
||||||
|
if godebugEnv != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg any
|
||||||
|
var ok bool
|
||||||
|
switch m := meta.Metadata.(type) {
|
||||||
|
case DockerContext:
|
||||||
|
cfg, ok = m.AdditionalFields[fieldName]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case map[string]any:
|
||||||
|
cfg, ok = m[fieldName]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := cfg.(string)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// set the GODEBUG environment variable with whatever was in the context
|
||||||
|
_ = os.Setenv(fieldName, v)
|
||||||
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) initialize() error {
|
func (cli *DockerCli) initialize() error {
|
||||||
cli.init.Do(func() {
|
cli.init.Do(func() {
|
||||||
cli.dockerEndpoint, cli.initErr = cli.getDockerEndPoint()
|
cli.dockerEndpoint, cli.initErr = cli.getDockerEndPoint()
|
||||||
@@ -483,7 +555,8 @@ func (cli *DockerCli) initialize() error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if cli.client == nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -496,6 +569,8 @@ func (cli *DockerCli) initialize() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply all the operation on the cli
|
// 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 {
|
func (cli *DockerCli) Apply(ops ...CLIOption) error {
|
||||||
for _, op := range ops {
|
for _, op := range ops {
|
||||||
if err := op(cli); err != nil {
|
if err := op(cli); err != nil {
|
||||||
@@ -527,15 +602,18 @@ type ServerInfo struct {
|
|||||||
// environment.
|
// environment.
|
||||||
func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
|
func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
|
||||||
defaultOps := []CLIOption{
|
defaultOps := []CLIOption{
|
||||||
WithContentTrustFromEnv(),
|
withContentTrustFromEnv(),
|
||||||
WithDefaultContextStoreConfig(),
|
WithDefaultContextStoreConfig(),
|
||||||
WithStandardStreams(),
|
WithStandardStreams(),
|
||||||
|
WithUserAgent(UserAgent()),
|
||||||
}
|
}
|
||||||
ops = append(defaultOps, ops...)
|
ops = append(defaultOps, ops...)
|
||||||
|
|
||||||
cli := &DockerCli{baseCtx: context.Background()}
|
cli := &DockerCli{baseCtx: context.Background()}
|
||||||
if err := cli.Apply(ops...); err != nil {
|
for _, op := range ops {
|
||||||
return nil, err
|
if err := op(cli); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return cli, nil
|
return cli, nil
|
||||||
}
|
}
|
||||||
@@ -551,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 {
|
func UserAgent() string {
|
||||||
return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")"
|
return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")"
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-50
@@ -75,8 +75,8 @@ func WithErrorStream(err io.Writer) CLIOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
|
// withContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
|
||||||
func WithContentTrustFromEnv() CLIOption {
|
func withContentTrustFromEnv() CLIOption {
|
||||||
return func(cli *DockerCli) error {
|
return func(cli *DockerCli) error {
|
||||||
cli.contentTrust = false
|
cli.contentTrust = false
|
||||||
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
|
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.
|
// 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 {
|
func WithContentTrust(enabled bool) CLIOption {
|
||||||
return func(cli *DockerCli) error {
|
return func(cli *DockerCli) error {
|
||||||
cli.contentTrust = enabled
|
cli.contentTrust = enabled
|
||||||
@@ -180,61 +189,70 @@ const envOverrideHTTPHeaders = "DOCKER_CUSTOM_HEADERS"
|
|||||||
// override headers with the same name).
|
// 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.
|
// 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 {
|
func withCustomHeadersFromEnv() (client.Opt, error) {
|
||||||
return func(apiClient *client.Client) error {
|
value := os.Getenv(envOverrideHTTPHeaders)
|
||||||
value := os.Getenv(envOverrideHTTPHeaders)
|
if value == "" {
|
||||||
if value == "" {
|
return nil, nil
|
||||||
return nil
|
}
|
||||||
}
|
csvReader := csv.NewReader(strings.NewReader(value))
|
||||||
csvReader := csv.NewReader(strings.NewReader(value))
|
fields, err := csvReader.Read()
|
||||||
fields, err := csvReader.Read()
|
if err != nil {
|
||||||
if err != nil {
|
return nil, invalidParameter(errors.Errorf(
|
||||||
return invalidParameter(errors.Errorf(
|
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
|
||||||
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
|
envOverrideHTTPHeaders,
|
||||||
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{}
|
env[http.CanonicalHeaderKey(k)] = v
|
||||||
for _, kv := range fields {
|
}
|
||||||
k, v, hasValue := strings.Cut(kv, "=")
|
|
||||||
|
|
||||||
// Only strip whitespace in keys; preserve whitespace in values.
|
if len(env) == 0 {
|
||||||
k = strings.TrimSpace(k)
|
// 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 == "" {
|
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
|
||||||
return invalidParameter(errors.Errorf(
|
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
|
||||||
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
|
return client.WithHTTPHeaders(env), nil
|
||||||
envOverrideHTTPHeaders, kv,
|
}
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't currently allow empty key=value pairs, and produce an error.
|
// WithUserAgent configures the User-Agent string for cli HTTP requests.
|
||||||
// This is something we could allow in future (e.g. to read value
|
func WithUserAgent(userAgent string) CLIOption {
|
||||||
// from an environment variable with the same name). In the meantime,
|
return func(cli *DockerCli) error {
|
||||||
// produce an error to prevent users from depending on this.
|
if userAgent == "" {
|
||||||
if !hasValue {
|
return errors.New("user agent cannot be blank")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
cli.userAgent = userAgent
|
||||||
if len(env) == 0 {
|
return nil
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
func contentTrustEnabled(t *testing.T) bool {
|
func contentTrustEnabled(t *testing.T) bool {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var cli DockerCli
|
var cli DockerCli
|
||||||
assert.NilError(t, WithContentTrustFromEnv()(&cli))
|
assert.NilError(t, withContentTrustFromEnv()(&cli))
|
||||||
return cli.contentTrust
|
return cli.contentTrust
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+47
-3
@@ -18,6 +18,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/cli/cli/config"
|
"github.com/docker/cli/cli/config"
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
|
"github.com/docker/cli/cli/context/store"
|
||||||
"github.com/docker/cli/cli/flags"
|
"github.com/docker/cli/cli/flags"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
@@ -188,16 +189,16 @@ func TestInitializeFromClient(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
apiclient := &fakeClient{
|
apiClient := &fakeClient{
|
||||||
pingFunc: tc.pingFunc,
|
pingFunc: tc.pingFunc,
|
||||||
version: defaultVersion,
|
version: defaultVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := &DockerCli{client: apiclient}
|
cli := &DockerCli{client: apiClient}
|
||||||
err := cli.Initialize(flags.NewClientOptions())
|
err := cli.Initialize(flags.NewClientOptions())
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.DeepEqual(t, cli.ServerInfo(), tc.expectedServer)
|
assert.DeepEqual(t, cli.ServerInfo(), tc.expectedServer)
|
||||||
assert.Equal(t, apiclient.negotiated, tc.negotiated)
|
assert.Equal(t, apiClient.negotiated, tc.negotiated)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,3 +354,46 @@ func TestHooksEnabled(t *testing.T) {
|
|||||||
assert.Check(t, !cli.HooksEnabled())
|
assert.Check(t, !cli.HooksEnabled())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetGoDebug(t *testing.T) {
|
||||||
|
t.Run("GODEBUG already set", func(t *testing.T) {
|
||||||
|
t.Setenv("GODEBUG", "val1,val2")
|
||||||
|
meta := store.Metadata{}
|
||||||
|
setGoDebug(meta)
|
||||||
|
assert.Equal(t, "val1,val2", os.Getenv("GODEBUG"))
|
||||||
|
})
|
||||||
|
t.Run("GODEBUG in context metadata can set env", func(t *testing.T) {
|
||||||
|
meta := store.Metadata{
|
||||||
|
Metadata: DockerContext{
|
||||||
|
AdditionalFields: map[string]any{
|
||||||
|
"GODEBUG": "val1,val2=1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
setGoDebug(meta)
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,69 +29,127 @@ import (
|
|||||||
func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
// commonly used shorthands
|
// commonly used shorthands
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
container.NewRunCommand(dockerCli),
|
container.NewRunCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
container.NewExecCommand(dockerCli),
|
container.NewExecCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
container.NewPsCommand(dockerCli),
|
container.NewPsCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
image.NewBuildCommand(dockerCli),
|
image.NewBuildCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
image.NewPullCommand(dockerCli),
|
image.NewPullCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
image.NewPushCommand(dockerCli),
|
image.NewPushCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
image.NewImagesCommand(dockerCli),
|
image.NewImagesCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
registry.NewLoginCommand(dockerCli),
|
registry.NewLoginCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
registry.NewLogoutCommand(dockerCli),
|
registry.NewLogoutCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
registry.NewSearchCommand(dockerCli),
|
registry.NewSearchCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
system.NewVersionCommand(dockerCli),
|
system.NewVersionCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
system.NewInfoCommand(dockerCli),
|
system.NewInfoCommand(dockerCli),
|
||||||
|
|
||||||
// management commands
|
// management commands
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
builder.NewBakeStubCommand(dockerCli),
|
builder.NewBakeStubCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
builder.NewBuilderCommand(dockerCli),
|
builder.NewBuilderCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
checkpoint.NewCheckpointCommand(dockerCli),
|
checkpoint.NewCheckpointCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
container.NewContainerCommand(dockerCli),
|
container.NewContainerCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
context.NewContextCommand(dockerCli),
|
context.NewContextCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
image.NewImageCommand(dockerCli),
|
image.NewImageCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
manifest.NewManifestCommand(dockerCli),
|
manifest.NewManifestCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
network.NewNetworkCommand(dockerCli),
|
network.NewNetworkCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
plugin.NewPluginCommand(dockerCli),
|
plugin.NewPluginCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
system.NewSystemCommand(dockerCli),
|
system.NewSystemCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
trust.NewTrustCommand(dockerCli),
|
trust.NewTrustCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
volume.NewVolumeCommand(dockerCli),
|
volume.NewVolumeCommand(dockerCli),
|
||||||
|
|
||||||
// orchestration (swarm) commands
|
// orchestration (swarm) commands
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
config.NewConfigCommand(dockerCli),
|
config.NewConfigCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
node.NewNodeCommand(dockerCli),
|
node.NewNodeCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
secret.NewSecretCommand(dockerCli),
|
secret.NewSecretCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
service.NewServiceCommand(dockerCli),
|
service.NewServiceCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
stack.NewStackCommand(dockerCli),
|
stack.NewStackCommand(dockerCli),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
swarm.NewSwarmCommand(dockerCli),
|
swarm.NewSwarmCommand(dockerCli),
|
||||||
|
|
||||||
// legacy commands may be hidden
|
// legacy commands may be hidden
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewAttachCommand(dockerCli)),
|
hide(container.NewAttachCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewCommitCommand(dockerCli)),
|
hide(container.NewCommitCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewCopyCommand(dockerCli)),
|
hide(container.NewCopyCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewCreateCommand(dockerCli)),
|
hide(container.NewCreateCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewDiffCommand(dockerCli)),
|
hide(container.NewDiffCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewExportCommand(dockerCli)),
|
hide(container.NewExportCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewKillCommand(dockerCli)),
|
hide(container.NewKillCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewLogsCommand(dockerCli)),
|
hide(container.NewLogsCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewPauseCommand(dockerCli)),
|
hide(container.NewPauseCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewPortCommand(dockerCli)),
|
hide(container.NewPortCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewRenameCommand(dockerCli)),
|
hide(container.NewRenameCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewRestartCommand(dockerCli)),
|
hide(container.NewRestartCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewRmCommand(dockerCli)),
|
hide(container.NewRmCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewStartCommand(dockerCli)),
|
hide(container.NewStartCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewStatsCommand(dockerCli)),
|
hide(container.NewStatsCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewStopCommand(dockerCli)),
|
hide(container.NewStopCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewTopCommand(dockerCli)),
|
hide(container.NewTopCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewUnpauseCommand(dockerCli)),
|
hide(container.NewUnpauseCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewUpdateCommand(dockerCli)),
|
hide(container.NewUpdateCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(container.NewWaitCommand(dockerCli)),
|
hide(container.NewWaitCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(image.NewHistoryCommand(dockerCli)),
|
hide(image.NewHistoryCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(image.NewImportCommand(dockerCli)),
|
hide(image.NewImportCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(image.NewLoadCommand(dockerCli)),
|
hide(image.NewLoadCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(image.NewRemoveCommand(dockerCli)),
|
hide(image.NewRemoveCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(image.NewSaveCommand(dockerCli)),
|
hide(image.NewSaveCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(image.NewTagCommand(dockerCli)),
|
hide(image.NewTagCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(system.NewEventsCommand(dockerCli)),
|
hide(system.NewEventsCommand(dockerCli)),
|
||||||
|
//nolint:staticcheck // TODO: Remove when migration to cli/internal/commands.Register is complete. (see #6283)
|
||||||
hide(system.NewInspectCommand(dockerCli)),
|
hide(system.NewInspectCommand(dockerCli)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,6 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidArgsFn a function to be used by cobra command as `ValidArgsFunction` to offer command line completion.
|
|
||||||
//
|
|
||||||
// Deprecated: use [cobra.CompletionFunc].
|
|
||||||
type ValidArgsFn = cobra.CompletionFunc
|
|
||||||
|
|
||||||
// APIClientProvider provides a method to get an [client.APIClient], initializing
|
// APIClientProvider provides a method to get an [client.APIClient], initializing
|
||||||
// it if needed.
|
// it if needed.
|
||||||
//
|
//
|
||||||
@@ -146,7 +141,9 @@ func FileNames(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCom
|
|||||||
return nil, cobra.ShellCompDirectiveDefault
|
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) {
|
func NoComplete(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
func TestCompletePlatforms(t *testing.T) {
|
||||||
values, directives := Platforms(nil, nil, "")
|
values, directives := Platforms(nil, nil, "")
|
||||||
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
||||||
|
|||||||
@@ -9,22 +9,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewConfigCommand returns a cobra command for `config` subcommands
|
// NewConfigCommand returns a cobra command for `config` subcommands
|
||||||
func NewConfigCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewConfigCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newConfigCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfigCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "config",
|
Use: "config",
|
||||||
Short: "Manage Swarm configs",
|
Short: "Manage Swarm configs",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: command.ShowHelp(dockerCli.Err()),
|
RunE: command.ShowHelp(dockerCLI.Err()),
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"version": "1.30",
|
"version": "1.30",
|
||||||
"swarm": "manager",
|
"swarm": "manager",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
newConfigListCommand(dockerCli),
|
newConfigListCommand(dockerCLI),
|
||||||
newConfigCreateCommand(dockerCli),
|
newConfigCreateCommand(dockerCLI),
|
||||||
newConfigInspectCommand(dockerCli),
|
newConfigInspectCommand(dockerCLI),
|
||||||
newConfigRemoveCommand(dockerCli),
|
newConfigRemoveCommand(dockerCLI),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/completion"
|
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/moby/sys/sequential"
|
"github.com/moby/sys/sequential"
|
||||||
@@ -16,6 +15,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// CreateOptions specifies some options that are used when creating a config.
|
// CreateOptions specifies some options that are used when creating a config.
|
||||||
|
//
|
||||||
|
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||||
type CreateOptions struct {
|
type CreateOptions struct {
|
||||||
Name string
|
Name string
|
||||||
TemplateDriver string
|
TemplateDriver string
|
||||||
@@ -23,9 +24,17 @@ type CreateOptions struct {
|
|||||||
Labels opts.ListOpts
|
Labels opts.ListOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command {
|
// createOptions specifies some options that are used when creating a config.
|
||||||
createOpts := CreateOptions{
|
type createOptions struct {
|
||||||
Labels: opts.NewListOpts(opts.ValidateLabel),
|
name string
|
||||||
|
templateDriver string
|
||||||
|
file string
|
||||||
|
labels opts.ListOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfigCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
createOpts := createOptions{
|
||||||
|
labels: opts.NewListOpts(opts.ValidateLabel),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -33,39 +42,51 @@ func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Short: "Create a config from a file or STDIN",
|
Short: "Create a config from a file or STDIN",
|
||||||
Args: cli.ExactArgs(2),
|
Args: cli.ExactArgs(2),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
createOpts.Name = args[0]
|
createOpts.name = args[0]
|
||||||
createOpts.File = args[1]
|
createOpts.file = args[1]
|
||||||
return RunConfigCreate(cmd.Context(), dockerCli, createOpts)
|
return runCreate(cmd.Context(), dockerCLI, createOpts)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.NoComplete,
|
ValidArgsFunction: cobra.NoFileCompletions,
|
||||||
}
|
}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.VarP(&createOpts.Labels, "label", "l", "Config labels")
|
flags.VarP(&createOpts.labels, "label", "l", "Config labels")
|
||||||
flags.StringVar(&createOpts.TemplateDriver, "template-driver", "", "Template driver")
|
flags.StringVar(&createOpts.templateDriver, "template-driver", "", "Template driver")
|
||||||
flags.SetAnnotation("template-driver", "version", []string{"1.37"})
|
_ = flags.SetAnnotation("template-driver", "version", []string{"1.37"})
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunConfigCreate creates a config with the given options.
|
// RunConfigCreate creates a config with the given options.
|
||||||
|
//
|
||||||
|
// Deprecated: this function was for internal use and will be removed in the next release.
|
||||||
func RunConfigCreate(ctx context.Context, dockerCLI command.Cli, options CreateOptions) error {
|
func RunConfigCreate(ctx context.Context, dockerCLI command.Cli, options CreateOptions) error {
|
||||||
|
return runCreate(ctx, dockerCLI, createOptions{
|
||||||
|
name: options.Name,
|
||||||
|
templateDriver: options.TemplateDriver,
|
||||||
|
file: options.File,
|
||||||
|
labels: options.Labels,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCreate creates a config with the given options.
|
||||||
|
func runCreate(ctx context.Context, dockerCLI command.Cli, options createOptions) error {
|
||||||
apiClient := dockerCLI.Client()
|
apiClient := dockerCLI.Client()
|
||||||
|
|
||||||
configData, err := readConfigData(dockerCLI.In(), options.File)
|
configData, err := readConfigData(dockerCLI.In(), options.file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Errorf("Error reading content from %q: %v", options.File, err)
|
return errors.Errorf("Error reading content from %q: %v", options.file, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
spec := swarm.ConfigSpec{
|
spec := swarm.ConfigSpec{
|
||||||
Annotations: swarm.Annotations{
|
Annotations: swarm.Annotations{
|
||||||
Name: options.Name,
|
Name: options.name,
|
||||||
Labels: opts.ConvertKVStringsToMap(options.Labels.GetSlice()),
|
Labels: opts.ConvertKVStringsToMap(options.labels.GetSlice()),
|
||||||
},
|
},
|
||||||
Data: configData,
|
Data: configData,
|
||||||
}
|
}
|
||||||
if options.TemplateDriver != "" {
|
if options.templateDriver != "" {
|
||||||
spec.Templating = &swarm.Driver{
|
spec.Templating = &swarm.Driver{
|
||||||
Name: options.TemplateDriver,
|
Name: options.templateDriver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r, err := apiClient.ConfigCreate(ctx, spec)
|
r, err := apiClient.ConfigCreate(ctx, spec)
|
||||||
|
|||||||
@@ -30,7 +30,14 @@ Data:
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewFormat returns a Format for rendering using a config Context
|
// NewFormat returns a Format for rendering using a config Context
|
||||||
|
//
|
||||||
|
// Deprecated: this function was only used internally and will be removed in the next release.
|
||||||
func NewFormat(source string, quiet bool) formatter.Format {
|
func NewFormat(source string, quiet bool) formatter.Format {
|
||||||
|
return newFormat(source, quiet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFormat returns a Format for rendering using a configContext.
|
||||||
|
func newFormat(source string, quiet bool) formatter.Format {
|
||||||
switch source {
|
switch source {
|
||||||
case formatter.PrettyFormatKey:
|
case formatter.PrettyFormatKey:
|
||||||
return configInspectPrettyTemplate
|
return configInspectPrettyTemplate
|
||||||
@@ -44,7 +51,14 @@ func NewFormat(source string, quiet bool) formatter.Format {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FormatWrite writes the context
|
// FormatWrite writes the context
|
||||||
func FormatWrite(ctx formatter.Context, configs []swarm.Config) error {
|
//
|
||||||
|
// Deprecated: this function was only used internally and will be removed in the next release.
|
||||||
|
func FormatWrite(fmtCtx formatter.Context, configs []swarm.Config) error {
|
||||||
|
return formatWrite(fmtCtx, configs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatWrite writes the context
|
||||||
|
func formatWrite(fmtCtx formatter.Context, configs []swarm.Config) error {
|
||||||
render := func(format func(subContext formatter.SubContext) error) error {
|
render := func(format func(subContext formatter.SubContext) error) error {
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
configCtx := &configContext{c: config}
|
configCtx := &configContext{c: config}
|
||||||
@@ -54,7 +68,7 @@ func FormatWrite(ctx formatter.Context, configs []swarm.Config) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return ctx.Write(newConfigContext(), render)
|
return fmtCtx.Write(newConfigContext(), render)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigContext() *configContext {
|
func newConfigContext() *configContext {
|
||||||
@@ -115,9 +129,16 @@ func (c *configContext) Label(name string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InspectFormatWrite renders the context for a list of configs
|
// InspectFormatWrite renders the context for a list of configs
|
||||||
func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.GetRefFunc) error {
|
//
|
||||||
if ctx.Format != configInspectPrettyTemplate {
|
// Deprecated: this function was only used internally and will be removed in the next release.
|
||||||
return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef)
|
func InspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef inspect.GetRefFunc) error {
|
||||||
|
return inspectFormatWrite(fmtCtx, refs, getRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspectFormatWrite renders the context for a list of configs
|
||||||
|
func inspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef inspect.GetRefFunc) error {
|
||||||
|
if fmtCtx.Format != configInspectPrettyTemplate {
|
||||||
|
return inspect.Inspect(fmtCtx.Output, refs, string(fmtCtx.Format), getRef)
|
||||||
}
|
}
|
||||||
render := func(format func(subContext formatter.SubContext) error) error {
|
render := func(format func(subContext formatter.SubContext) error) error {
|
||||||
for _, ref := range refs {
|
for _, ref := range refs {
|
||||||
@@ -135,7 +156,7 @@ func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.Get
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return ctx.Write(&configInspectContext{}, render)
|
return fmtCtx.Write(&configInspectContext{}, render)
|
||||||
}
|
}
|
||||||
|
|
||||||
type configInspectContext struct {
|
type configInspectContext struct {
|
||||||
|
|||||||
@@ -27,21 +27,21 @@ func TestConfigContextFormatWrite(t *testing.T) {
|
|||||||
},
|
},
|
||||||
// Table format
|
// Table format
|
||||||
{
|
{
|
||||||
formatter.Context{Format: NewFormat("table", false)},
|
formatter.Context{Format: newFormat("table", false)},
|
||||||
`ID NAME CREATED UPDATED
|
`ID NAME CREATED UPDATED
|
||||||
1 passwords Less than a second ago Less than a second ago
|
1 passwords Less than a second ago Less than a second ago
|
||||||
2 id_rsa Less than a second ago Less than a second ago
|
2 id_rsa Less than a second ago Less than a second ago
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
formatter.Context{Format: NewFormat("table {{.Name}}", true)},
|
formatter.Context{Format: newFormat("table {{.Name}}", true)},
|
||||||
`NAME
|
`NAME
|
||||||
passwords
|
passwords
|
||||||
id_rsa
|
id_rsa
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
formatter.Context{Format: NewFormat("{{.ID}}-{{.Name}}", false)},
|
formatter.Context{Format: newFormat("{{.ID}}-{{.Name}}", false)},
|
||||||
`1-passwords
|
`1-passwords
|
||||||
2-id_rsa
|
2-id_rsa
|
||||||
`,
|
`,
|
||||||
@@ -64,7 +64,7 @@ id_rsa
|
|||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
tc.context.Output = &out
|
tc.context.Output = &out
|
||||||
if err := FormatWrite(tc.context, configs); err != nil {
|
if err := formatWrite(tc.context, configs); err != nil {
|
||||||
assert.ErrorContains(t, err, tc.expected)
|
assert.ErrorContains(t, err, tc.expected)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, out.String(), tc.expected)
|
assert.Equal(t, out.String(), tc.expected)
|
||||||
|
|||||||
@@ -16,57 +16,76 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// InspectOptions contains options for the docker config inspect command.
|
// InspectOptions contains options for the docker config inspect command.
|
||||||
|
//
|
||||||
|
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||||
type InspectOptions struct {
|
type InspectOptions struct {
|
||||||
Names []string
|
Names []string
|
||||||
Format string
|
Format string
|
||||||
Pretty bool
|
Pretty bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigInspectCommand(dockerCli command.Cli) *cobra.Command {
|
// inspectOptions contains options for the docker config inspect command.
|
||||||
opts := InspectOptions{}
|
type inspectOptions struct {
|
||||||
|
names []string
|
||||||
|
format string
|
||||||
|
pretty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfigInspectCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
opts := inspectOptions{}
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "inspect [OPTIONS] CONFIG [CONFIG...]",
|
Use: "inspect [OPTIONS] CONFIG [CONFIG...]",
|
||||||
Short: "Display detailed information on one or more configs",
|
Short: "Display detailed information on one or more configs",
|
||||||
Args: cli.RequiresMinArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.Names = args
|
opts.names = args
|
||||||
return RunConfigInspect(cmd.Context(), dockerCli, opts)
|
return runInspect(cmd.Context(), dockerCLI, opts)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
return completeNames(dockerCli)(cmd, args, toComplete)
|
return completeNames(dockerCLI)(cmd, args, toComplete)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&opts.Format, "format", "f", "", flagsHelper.InspectFormatHelp)
|
cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp)
|
||||||
cmd.Flags().BoolVar(&opts.Pretty, "pretty", false, "Print the information in a human friendly format")
|
cmd.Flags().BoolVar(&opts.pretty, "pretty", false, "Print the information in a human friendly format")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunConfigInspect inspects the given Swarm config.
|
// RunConfigInspect inspects the given Swarm config.
|
||||||
|
//
|
||||||
|
// Deprecated: this function was for internal use and will be removed in the next release.
|
||||||
func RunConfigInspect(ctx context.Context, dockerCLI command.Cli, opts InspectOptions) error {
|
func RunConfigInspect(ctx context.Context, dockerCLI command.Cli, opts InspectOptions) error {
|
||||||
|
return runInspect(ctx, dockerCLI, inspectOptions{
|
||||||
|
names: opts.Names,
|
||||||
|
format: opts.Format,
|
||||||
|
pretty: opts.Pretty,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// runInspect inspects the given Swarm config.
|
||||||
|
func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error {
|
||||||
apiClient := dockerCLI.Client()
|
apiClient := dockerCLI.Client()
|
||||||
|
|
||||||
if opts.Pretty {
|
if opts.pretty {
|
||||||
opts.Format = "pretty"
|
opts.format = "pretty"
|
||||||
}
|
}
|
||||||
|
|
||||||
getRef := func(id string) (any, []byte, error) {
|
getRef := func(id string) (any, []byte, error) {
|
||||||
return apiClient.ConfigInspectWithRaw(ctx, id)
|
return apiClient.ConfigInspectWithRaw(ctx, id)
|
||||||
}
|
}
|
||||||
f := opts.Format
|
|
||||||
|
|
||||||
// check if the user is trying to apply a template to the pretty format, which
|
// check if the user is trying to apply a template to the pretty format, which
|
||||||
// is not supported
|
// is not supported
|
||||||
if strings.HasPrefix(f, "pretty") && f != "pretty" {
|
if strings.HasPrefix(opts.format, "pretty") && opts.format != "pretty" {
|
||||||
return errors.New("cannot supply extra formatting options to the pretty template")
|
return errors.New("cannot supply extra formatting options to the pretty template")
|
||||||
}
|
}
|
||||||
|
|
||||||
configCtx := formatter.Context{
|
configCtx := formatter.Context{
|
||||||
Output: dockerCLI.Out(),
|
Output: dockerCLI.Out(),
|
||||||
Format: NewFormat(f, false),
|
Format: newFormat(opts.format, false),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := InspectFormatWrite(configCtx, opts.Names, getRef); err != nil {
|
if err := inspectFormatWrite(configCtx, opts.names, getRef); err != nil {
|
||||||
return cli.StatusError{StatusCode: 1, Status: err.Error()}
|
return cli.StatusError{StatusCode: 1, Status: err.Error()}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+32
-13
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"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/formatter"
|
||||||
flagsHelper "github.com/docker/cli/cli/flags"
|
flagsHelper "github.com/docker/cli/cli/flags"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
@@ -16,14 +15,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ListOptions contains options for the docker config ls command.
|
// ListOptions contains options for the docker config ls command.
|
||||||
|
//
|
||||||
|
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||||
type ListOptions struct {
|
type ListOptions struct {
|
||||||
Quiet bool
|
Quiet bool
|
||||||
Format string
|
Format string
|
||||||
Filter opts.FilterOpt
|
Filter opts.FilterOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigListCommand(dockerCli command.Cli) *cobra.Command {
|
// listOptions contains options for the docker config ls command.
|
||||||
listOpts := ListOptions{Filter: opts.NewFilterOpt()}
|
type listOptions struct {
|
||||||
|
quiet bool
|
||||||
|
format string
|
||||||
|
filter opts.FilterOpt
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfigListCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
listOpts := listOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls [OPTIONS]",
|
Use: "ls [OPTIONS]",
|
||||||
@@ -31,31 +39,42 @@ func newConfigListCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Short: "List configs",
|
Short: "List configs",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return RunConfigList(cmd.Context(), dockerCli, listOpts)
|
return runList(cmd.Context(), dockerCLI, listOpts)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.NoComplete,
|
ValidArgsFunction: cobra.NoFileCompletions,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&listOpts.Quiet, "quiet", "q", false, "Only display IDs")
|
flags.BoolVarP(&listOpts.quiet, "quiet", "q", false, "Only display IDs")
|
||||||
flags.StringVar(&listOpts.Format, "format", "", flagsHelper.FormatHelp)
|
flags.StringVar(&listOpts.format, "format", "", flagsHelper.FormatHelp)
|
||||||
flags.VarP(&listOpts.Filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&listOpts.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunConfigList lists Swarm configs.
|
// RunConfigList lists Swarm configs.
|
||||||
|
//
|
||||||
|
// Deprecated: this function was for internal use and will be removed in the next release.
|
||||||
func RunConfigList(ctx context.Context, dockerCLI command.Cli, options ListOptions) error {
|
func RunConfigList(ctx context.Context, dockerCLI command.Cli, options ListOptions) error {
|
||||||
|
return runList(ctx, dockerCLI, listOptions{
|
||||||
|
quiet: options.Quiet,
|
||||||
|
format: options.Format,
|
||||||
|
filter: options.Filter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// runList lists Swarm configs.
|
||||||
|
func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) error {
|
||||||
apiClient := dockerCLI.Client()
|
apiClient := dockerCLI.Client()
|
||||||
|
|
||||||
configs, err := apiClient.ConfigList(ctx, swarm.ConfigListOptions{Filters: options.Filter.Value()})
|
configs, err := apiClient.ConfigList(ctx, swarm.ConfigListOptions{Filters: options.filter.Value()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
format := options.Format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCLI.ConfigFile().ConfigFormat) > 0 && !options.Quiet {
|
if len(dockerCLI.ConfigFile().ConfigFormat) > 0 && !options.quiet {
|
||||||
format = dockerCLI.ConfigFile().ConfigFormat
|
format = dockerCLI.ConfigFile().ConfigFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
@@ -68,7 +87,7 @@ func RunConfigList(ctx context.Context, dockerCLI command.Cli, options ListOptio
|
|||||||
|
|
||||||
configCtx := formatter.Context{
|
configCtx := formatter.Context{
|
||||||
Output: dockerCLI.Out(),
|
Output: dockerCLI.Out(),
|
||||||
Format: NewFormat(format, options.Quiet),
|
Format: newFormat(format, options.quiet),
|
||||||
}
|
}
|
||||||
return FormatWrite(configCtx, configs)
|
return formatWrite(configCtx, configs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,34 +11,40 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RemoveOptions contains options for the docker config rm command.
|
// RemoveOptions contains options for the docker config rm command.
|
||||||
|
//
|
||||||
|
// Deprecated: this type was for internal use and will be removed in the next release.
|
||||||
type RemoveOptions struct {
|
type RemoveOptions struct {
|
||||||
Names []string
|
Names []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
func newConfigRemoveCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "rm CONFIG [CONFIG...]",
|
Use: "rm CONFIG [CONFIG...]",
|
||||||
Aliases: []string{"remove"},
|
Aliases: []string{"remove"},
|
||||||
Short: "Remove one or more configs",
|
Short: "Remove one or more configs",
|
||||||
Args: cli.RequiresMinArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts := RemoveOptions{
|
return runRemove(cmd.Context(), dockerCLI, args)
|
||||||
Names: args,
|
|
||||||
}
|
|
||||||
return RunConfigRemove(cmd.Context(), dockerCli, opts)
|
|
||||||
},
|
},
|
||||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
return completeNames(dockerCli)(cmd, args, toComplete)
|
return completeNames(dockerCLI)(cmd, args, toComplete)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunConfigRemove removes the given Swarm configs.
|
// RunConfigRemove removes the given Swarm configs.
|
||||||
|
//
|
||||||
|
// Deprecated: this function was for internal use and will be removed in the next release.
|
||||||
func RunConfigRemove(ctx context.Context, dockerCLI command.Cli, opts RemoveOptions) error {
|
func RunConfigRemove(ctx context.Context, dockerCLI command.Cli, opts RemoveOptions) error {
|
||||||
|
return runRemove(ctx, dockerCLI, opts.Names)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runRemove removes the given Swarm configs.
|
||||||
|
func runRemove(ctx context.Context, dockerCLI command.Cli, names []string) error {
|
||||||
apiClient := dockerCLI.Client()
|
apiClient := dockerCLI.Client()
|
||||||
|
|
||||||
var errs []error
|
var errs []error
|
||||||
for _, name := range opts.Names {
|
for _, name := range names {
|
||||||
if err := apiClient.ConfigRemove(ctx, name); err != nil {
|
if err := apiClient.ConfigRemove(ctx, name); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -41,7 +41,13 @@ func inspectContainerAndCheckState(ctx context.Context, apiClient client.APIClie
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewAttachCommand creates a new cobra.Command for `docker attach`
|
// NewAttachCommand creates a new cobra.Command for `docker attach`
|
||||||
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
func NewAttachCommand(dockerCLI command.Cli) *cobra.Command {
|
func NewAttachCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newAttachCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAttachCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts AttachOptions
|
var opts AttachOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func TestNewAttachCommandErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc}))
|
cmd := newAttachCommand(test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc}))
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs(tc.args)
|
cmd.SetArgs(tc.args)
|
||||||
|
|||||||
@@ -7,39 +7,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewContainerCommand returns a cobra command for `container` subcommands
|
// NewContainerCommand returns a cobra command for `container` subcommands
|
||||||
func NewContainerCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewContainerCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newContainerCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newContainerCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "container",
|
Use: "container",
|
||||||
Short: "Manage containers",
|
Short: "Manage containers",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: command.ShowHelp(dockerCli.Err()),
|
RunE: command.ShowHelp(dockerCLI.Err()),
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
NewAttachCommand(dockerCli),
|
newAttachCommand(dockerCLI),
|
||||||
NewCommitCommand(dockerCli),
|
newCommitCommand(dockerCLI),
|
||||||
NewCopyCommand(dockerCli),
|
newCopyCommand(dockerCLI),
|
||||||
NewCreateCommand(dockerCli),
|
newCreateCommand(dockerCLI),
|
||||||
NewDiffCommand(dockerCli),
|
newDiffCommand(dockerCLI),
|
||||||
NewExecCommand(dockerCli),
|
newExecCommand(dockerCLI),
|
||||||
NewExportCommand(dockerCli),
|
newExportCommand(dockerCLI),
|
||||||
NewKillCommand(dockerCli),
|
newKillCommand(dockerCLI),
|
||||||
NewLogsCommand(dockerCli),
|
newLogsCommand(dockerCLI),
|
||||||
NewPauseCommand(dockerCli),
|
newPauseCommand(dockerCLI),
|
||||||
NewPortCommand(dockerCli),
|
newPortCommand(dockerCLI),
|
||||||
NewRenameCommand(dockerCli),
|
newRenameCommand(dockerCLI),
|
||||||
NewRestartCommand(dockerCli),
|
newRestartCommand(dockerCLI),
|
||||||
newRemoveCommand(dockerCli),
|
newRemoveCommand(dockerCLI),
|
||||||
NewRunCommand(dockerCli),
|
newRunCommand(dockerCLI),
|
||||||
NewStartCommand(dockerCli),
|
newStartCommand(dockerCLI),
|
||||||
NewStatsCommand(dockerCli),
|
newStatsCommand(dockerCLI),
|
||||||
NewStopCommand(dockerCli),
|
newStopCommand(dockerCLI),
|
||||||
NewTopCommand(dockerCli),
|
newTopCommand(dockerCLI),
|
||||||
NewUnpauseCommand(dockerCli),
|
newUnpauseCommand(dockerCLI),
|
||||||
NewUpdateCommand(dockerCli),
|
newUpdateCommand(dockerCLI),
|
||||||
NewWaitCommand(dockerCli),
|
newWaitCommand(dockerCLI),
|
||||||
newListCommand(dockerCli),
|
newListCommand(dockerCLI),
|
||||||
newInspectCommand(dockerCli),
|
newInspectCommand(dockerCLI),
|
||||||
NewPruneCommand(dockerCli),
|
newPruneCommand(dockerCLI),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,13 @@ type commitOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCommitCommand creates a new cobra.Command for `docker commit`
|
// NewCommitCommand creates a new cobra.Command for `docker commit`
|
||||||
func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewCommitCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newCommitCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommitCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var options commitOptions
|
var options commitOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -35,12 +41,12 @@ func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
options.reference = args[1]
|
options.reference = args[1]
|
||||||
}
|
}
|
||||||
return runCommit(cmd.Context(), dockerCli, &options)
|
return runCommit(cmd.Context(), dockerCLI, &options)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container commit, docker commit",
|
"aliases": "docker container commit, docker commit",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func TestRunCommit(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd := NewCommitCommand(cli)
|
cmd := newCommitCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetArgs(
|
cmd.SetArgs(
|
||||||
[]string{
|
[]string{
|
||||||
@@ -60,7 +60,7 @@ func TestRunCommitClientError(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd := NewCommitCommand(cli)
|
cmd := newCommitCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs([]string{"container-id"})
|
cmd.SetArgs([]string{"container-id"})
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func TestCompletePid(t *testing.T) {
|
|||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
containerListFunc: tc.containerListFunc,
|
containerListFunc: tc.containerListFunc,
|
||||||
})
|
})
|
||||||
completions, directive := completePid(cli)(NewRunCommand(cli), nil, tc.toComplete)
|
completions, directive := completePid(cli)(newRunCommand(cli), nil, tc.toComplete)
|
||||||
assert.Check(t, is.DeepEqual(completions, tc.expectedCompletions))
|
assert.Check(t, is.DeepEqual(completions, tc.expectedCompletions))
|
||||||
assert.Check(t, is.Equal(directive, tc.expectedDirective))
|
assert.Check(t, is.Equal(directive, tc.expectedDirective))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -122,7 +122,13 @@ func copyProgress(ctx context.Context, dst io.Writer, header string, total *int6
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCopyCommand creates a new `docker cp` command
|
// NewCopyCommand creates a new `docker cp` command
|
||||||
func NewCopyCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewCopyCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newCopyCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCopyCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts copyOptions
|
var opts copyOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -147,9 +153,9 @@ container source to stdout.`,
|
|||||||
opts.destination = args[1]
|
opts.destination = args[1]
|
||||||
if !cmd.Flag("quiet").Changed {
|
if !cmd.Flag("quiet").Changed {
|
||||||
// User did not specify "quiet" flag; suppress output if no terminal is attached
|
// User did not specify "quiet" flag; suppress output if no terminal is attached
|
||||||
opts.quiet = !dockerCli.Out().IsTerminal()
|
opts.quiet = !dockerCLI.Out().IsTerminal()
|
||||||
}
|
}
|
||||||
return runCopy(cmd.Context(), dockerCli, opts)
|
return runCopy(cmd.Context(), dockerCLI, opts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container cp, docker cp",
|
"aliases": "docker container cp, docker cp",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
cerrdefs "github.com/containerd/errdefs"
|
"github.com/containerd/errdefs"
|
||||||
"github.com/containerd/platforms"
|
"github.com/containerd/platforms"
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
@@ -52,7 +52,13 @@ type createOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCreateCommand creates a new cobra.Command for `docker create`
|
// NewCreateCommand creates a new cobra.Command for `docker create`
|
||||||
func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newCreateCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var options createOptions
|
var options createOptions
|
||||||
var copts *containerOptions
|
var copts *containerOptions
|
||||||
|
|
||||||
@@ -65,12 +71,12 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
copts.Args = args[1:]
|
copts.Args = args[1:]
|
||||||
}
|
}
|
||||||
return runCreate(cmd.Context(), dockerCli, cmd.Flags(), &options, copts)
|
return runCreate(cmd.Context(), dockerCLI, cmd.Flags(), &options, copts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container create, docker create",
|
"aliases": "docker container create, docker create",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ImageNames(dockerCli, -1),
|
ValidArgsFunction: completion.ImageNames(dockerCLI, -1),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
@@ -86,17 +92,20 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
// with hostname
|
// with hostname
|
||||||
flags.Bool("help", false, "Print usage")
|
flags.Bool("help", false, "Print usage")
|
||||||
|
|
||||||
command.AddPlatformFlag(flags, &options.platform)
|
// TODO(thaJeztah): consider adding platform as "image create option" on containerOptions
|
||||||
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
addPlatformFlag(flags, &options.platform)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
||||||
|
|
||||||
|
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
|
||||||
copts = addFlags(flags)
|
copts = addFlags(flags)
|
||||||
|
|
||||||
addCompletions(cmd, dockerCli)
|
addCompletions(cmd, dockerCLI)
|
||||||
|
|
||||||
flags.VisitAll(func(flag *pflag.Flag) {
|
flags.VisitAll(func(flag *pflag.Flag) {
|
||||||
// Set a default completion function if none was set. We don't look
|
// 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
|
// up if it does already have one set, because Cobra does this for
|
||||||
// us, and returns an error (which we ignore for this reason).
|
// 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
|
return cmd
|
||||||
@@ -341,7 +350,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
|
|||||||
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name)
|
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior.
|
// Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior.
|
||||||
if cerrdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing {
|
if errdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing {
|
||||||
if !options.quiet {
|
if !options.quiet {
|
||||||
// we don't want to write to stdout anything apart from container.ID
|
// we don't want to write to stdout anything apart from container.ID
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
|
_, _ = fmt.Fprintf(dockerCli.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
|
|||||||
t.Run(tc.PullPolicy, func(t *testing.T) {
|
t.Run(tc.PullPolicy, func(t *testing.T) {
|
||||||
pullCounter := 0
|
pullCounter := 0
|
||||||
|
|
||||||
client := &fakeClient{
|
apiClient := &fakeClient{
|
||||||
createContainerFunc: func(
|
createContainerFunc: func(
|
||||||
config *container.Config,
|
config *container.Config,
|
||||||
hostConfig *container.HostConfig,
|
hostConfig *container.HostConfig,
|
||||||
@@ -140,7 +140,7 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
|
|||||||
return system.Info{IndexServerAddress: "https://indexserver.example.com"}, nil
|
return system.Info{IndexServerAddress: "https://indexserver.example.com"}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fakeCLI := test.NewFakeCli(client)
|
fakeCLI := test.NewFakeCli(apiClient)
|
||||||
id, err := createContainer(context.Background(), fakeCLI, config, &createOptions{
|
id, err := createContainer(context.Background(), fakeCLI, config, &createOptions{
|
||||||
name: "name",
|
name: "name",
|
||||||
platform: runtime.GOOS,
|
platform: runtime.GOOS,
|
||||||
@@ -206,7 +206,7 @@ func TestCreateContainerValidateFlags(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := NewCreateCommand(test.NewFakeCli(&fakeClient{}))
|
cmd := newCreateCommand(test.NewFakeCli(&fakeClient{}))
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs(tc.args)
|
cmd.SetArgs(tc.args)
|
||||||
@@ -249,6 +249,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Setenv("DOCKER_CONTENT_TRUST", "true")
|
||||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||||
createContainerFunc: func(config *container.Config,
|
createContainerFunc: func(config *container.Config,
|
||||||
hostConfig *container.HostConfig,
|
hostConfig *container.HostConfig,
|
||||||
@@ -258,9 +259,9 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
|||||||
) (container.CreateResponse, error) {
|
) (container.CreateResponse, error) {
|
||||||
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
|
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
|
||||||
},
|
},
|
||||||
}, test.EnableContentTrust)
|
})
|
||||||
fakeCLI.SetNotaryClient(tc.notaryFunc)
|
fakeCLI.SetNotaryClient(tc.notaryFunc)
|
||||||
cmd := NewCreateCommand(fakeCLI)
|
cmd := newCreateCommand(fakeCLI)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs(tc.args)
|
cmd.SetArgs(tc.args)
|
||||||
@@ -314,7 +315,7 @@ func TestNewCreateCommandWithWarnings(t *testing.T) {
|
|||||||
return container.CreateResponse{Warnings: tc.warnings}, nil
|
return container.CreateResponse{Warnings: tc.warnings}, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := NewCreateCommand(fakeCLI)
|
cmd := newCreateCommand(fakeCLI)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetArgs(tc.args)
|
cmd.SetArgs(tc.args)
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
@@ -366,7 +367,7 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := NewCreateCommand(fakeCLI)
|
cmd := newCreateCommand(fakeCLI)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetArgs([]string{"image:tag"})
|
cmd.SetArgs([]string{"image:tag"})
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
|
|||||||
@@ -11,18 +11,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewDiffCommand creates a new cobra.Command for `docker diff`
|
// NewDiffCommand creates a new cobra.Command for `docker diff`
|
||||||
func NewDiffCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewDiffCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newDiffCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDiffCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "diff CONTAINER",
|
Use: "diff CONTAINER",
|
||||||
Short: "Inspect changes to files or directories on a container's filesystem",
|
Short: "Inspect changes to files or directories on a container's filesystem",
|
||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runDiff(cmd.Context(), dockerCli, args[0])
|
return runDiff(cmd.Context(), dockerCLI, args[0])
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container diff, docker diff",
|
"aliases": "docker container diff, docker diff",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +39,7 @@ func runDiff(ctx context.Context, dockerCLI command.Cli, containerID string) err
|
|||||||
}
|
}
|
||||||
diffCtx := formatter.Context{
|
diffCtx := formatter.Context{
|
||||||
Output: dockerCLI.Out(),
|
Output: dockerCLI.Out(),
|
||||||
Format: NewDiffFormat("{{.Type}} {{.Path}}"),
|
Format: newDiffFormat("{{.Type}} {{.Path}}"),
|
||||||
}
|
}
|
||||||
return DiffFormatWrite(diffCtx, changes)
|
return diffFormatWrite(diffCtx, changes)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func TestRunDiff(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd := NewDiffCommand(cli)
|
cmd := newDiffCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
|
|
||||||
cmd.SetArgs([]string{"container-id"})
|
cmd.SetArgs([]string{"container-id"})
|
||||||
@@ -68,7 +68,7 @@ func TestRunDiffClientError(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd := NewDiffCommand(cli)
|
cmd := newDiffCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import cerrdefs "github.com/containerd/errdefs"
|
import "github.com/containerd/errdefs"
|
||||||
|
|
||||||
func invalidParameter(err error) error {
|
func invalidParameter(err error) error {
|
||||||
if err == nil || cerrdefs.IsInvalidArgument(err) {
|
if err == nil || errdefs.IsInvalidArgument(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return invalidParameterErr{err}
|
return invalidParameterErr{err}
|
||||||
@@ -17,7 +17,7 @@ func (e invalidParameterErr) Unwrap() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func notFound(err error) error {
|
func notFound(err error) error {
|
||||||
if err == nil || cerrdefs.IsNotFound(err) {
|
if err == nil || errdefs.IsNotFound(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return notFoundErr{err}
|
return notFoundErr{err}
|
||||||
|
|||||||
@@ -40,7 +40,13 @@ func NewExecOptions() ExecOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewExecCommand creates a new cobra.Command for `docker exec`
|
// NewExecCommand creates a new cobra.Command for `docker exec`
|
||||||
func NewExecCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewExecCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newExecCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExecCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
options := NewExecOptions()
|
options := NewExecOptions()
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -50,9 +56,9 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
containerIDorName := args[0]
|
containerIDorName := args[0]
|
||||||
options.Command = args[1:]
|
options.Command = args[1:]
|
||||||
return RunExec(cmd.Context(), dockerCli, containerIDorName, options)
|
return RunExec(cmd.Context(), dockerCLI, containerIDorName, options)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(ctr container.Summary) bool {
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, false, func(ctr container.Summary) bool {
|
||||||
return ctr.State != container.StatePaused
|
return ctr.State != container.StatePaused
|
||||||
}),
|
}),
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
|
|||||||
@@ -234,13 +234,13 @@ func TestGetExecExitStatus(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testcase := range testcases {
|
for _, testcase := range testcases {
|
||||||
client := &fakeClient{
|
apiClient := &fakeClient{
|
||||||
execInspectFunc: func(id string) (container.ExecInspect, error) {
|
execInspectFunc: func(id string) (container.ExecInspect, error) {
|
||||||
assert.Check(t, is.Equal(execID, id))
|
assert.Check(t, is.Equal(execID, id))
|
||||||
return container.ExecInspect{ExitCode: testcase.exitCode}, testcase.inspectError
|
return container.ExecInspect{ExitCode: testcase.exitCode}, testcase.inspectError
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err := getExecExitStatus(context.Background(), client, execID)
|
err := getExecExitStatus(context.Background(), apiClient, execID)
|
||||||
assert.Check(t, is.Equal(testcase.expectedError, err))
|
assert.Check(t, is.Equal(testcase.expectedError, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,7 +263,7 @@ func TestNewExecCommandErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
fakeCLI := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})
|
fakeCLI := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})
|
||||||
cmd := NewExecCommand(fakeCLI)
|
cmd := newExecCommand(fakeCLI)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetArgs(tc.args)
|
cmd.SetArgs(tc.args)
|
||||||
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ type exportOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewExportCommand creates a new `docker export` command
|
// NewExportCommand creates a new `docker export` command
|
||||||
func NewExportCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewExportCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newExportCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExportCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts exportOptions
|
var opts exportOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -27,12 +33,12 @@ func NewExportCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.container = args[0]
|
opts.container = args[0]
|
||||||
return runExport(cmd.Context(), dockerCli, opts)
|
return runExport(cmd.Context(), dockerCLI, opts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container export, docker export",
|
"aliases": "docker container export, docker export",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, true),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestContainerExportOutputToFile(t *testing.T) {
|
|||||||
return io.NopCloser(strings.NewReader("bar")), nil
|
return io.NopCloser(strings.NewReader("bar")), nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := NewExportCommand(cli)
|
cmd := newExportCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetArgs([]string{"-o", dir.Join("foo"), "container"})
|
cmd.SetArgs([]string{"-o", dir.Join("foo"), "container"})
|
||||||
assert.NilError(t, cmd.Execute())
|
assert.NilError(t, cmd.Execute())
|
||||||
@@ -37,7 +37,7 @@ func TestContainerExportOutputToIrregularFile(t *testing.T) {
|
|||||||
return io.NopCloser(strings.NewReader("foo")), nil
|
return io.NopCloser(strings.NewReader("foo")), nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := NewExportCommand(cli)
|
cmd := newExportCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs([]string{"-o", "/dev/random", "container"})
|
cmd.SetArgs([]string{"-o", "/dev/random", "container"})
|
||||||
|
|||||||
@@ -13,7 +13,14 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewDiffFormat returns a format for use with a diff Context
|
// NewDiffFormat returns a format for use with a diff Context
|
||||||
|
//
|
||||||
|
// Deprecated: this function was only used internally and will be removed in the next release.
|
||||||
func NewDiffFormat(source string) formatter.Format {
|
func NewDiffFormat(source string) formatter.Format {
|
||||||
|
return newDiffFormat(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDiffFormat returns a format for use with a diff [formatter.Context].
|
||||||
|
func newDiffFormat(source string) formatter.Format {
|
||||||
if source == formatter.TableFormatKey {
|
if source == formatter.TableFormatKey {
|
||||||
return defaultDiffTableFormat
|
return defaultDiffTableFormat
|
||||||
}
|
}
|
||||||
@@ -21,16 +28,22 @@ func NewDiffFormat(source string) formatter.Format {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DiffFormatWrite writes formatted diff using the Context
|
// DiffFormatWrite writes formatted diff using the Context
|
||||||
func DiffFormatWrite(ctx formatter.Context, changes []container.FilesystemChange) error {
|
//
|
||||||
render := func(format func(subContext formatter.SubContext) error) error {
|
// Deprecated: this function was only used internally and will be removed in the next release.
|
||||||
|
func DiffFormatWrite(fmtCtx formatter.Context, changes []container.FilesystemChange) error {
|
||||||
|
return diffFormatWrite(fmtCtx, changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// diffFormatWrite writes formatted diff using the [formatter.Context].
|
||||||
|
func diffFormatWrite(fmtCtx formatter.Context, changes []container.FilesystemChange) error {
|
||||||
|
return fmtCtx.Write(newDiffContext(), func(format func(subContext formatter.SubContext) error) error {
|
||||||
for _, change := range changes {
|
for _, change := range changes {
|
||||||
if err := format(&diffContext{c: change}); err != nil {
|
if err := format(&diffContext{c: change}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
})
|
||||||
return ctx.Write(newDiffContext(), render)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type diffContext struct {
|
type diffContext struct {
|
||||||
@@ -39,12 +52,14 @@ type diffContext struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newDiffContext() *diffContext {
|
func newDiffContext() *diffContext {
|
||||||
diffCtx := diffContext{}
|
return &diffContext{
|
||||||
diffCtx.Header = formatter.SubHeaderContext{
|
HeaderContext: formatter.HeaderContext{
|
||||||
"Type": changeTypeHeader,
|
Header: formatter.SubHeaderContext{
|
||||||
"Path": pathHeader,
|
"Type": changeTypeHeader,
|
||||||
|
"Path": pathHeader,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return &diffCtx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *diffContext) MarshalJSON() ([]byte, error) {
|
func (d *diffContext) MarshalJSON() ([]byte, error) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func TestDiffContextFormatWrite(t *testing.T) {
|
|||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
formatter.Context{Format: NewDiffFormat("table")},
|
formatter.Context{Format: newDiffFormat("table")},
|
||||||
`CHANGE TYPE PATH
|
`CHANGE TYPE PATH
|
||||||
C /var/log/app.log
|
C /var/log/app.log
|
||||||
A /usr/app/app.js
|
A /usr/app/app.js
|
||||||
@@ -24,7 +24,7 @@ D /usr/app/old_app.js
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
formatter.Context{Format: NewDiffFormat("table {{.Path}}")},
|
formatter.Context{Format: newDiffFormat("table {{.Path}}")},
|
||||||
`PATH
|
`PATH
|
||||||
/var/log/app.log
|
/var/log/app.log
|
||||||
/usr/app/app.js
|
/usr/app/app.js
|
||||||
@@ -32,7 +32,7 @@ D /usr/app/old_app.js
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
formatter.Context{Format: NewDiffFormat("{{.Type}}: {{.Path}}")},
|
formatter.Context{Format: newDiffFormat("{{.Type}}: {{.Path}}")},
|
||||||
`C: /var/log/app.log
|
`C: /var/log/app.log
|
||||||
A: /usr/app/app.js
|
A: /usr/app/app.js
|
||||||
D: /usr/app/old_app.js
|
D: /usr/app/old_app.js
|
||||||
@@ -50,7 +50,7 @@ D: /usr/app/old_app.js
|
|||||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||||
out := bytes.NewBufferString("")
|
out := bytes.NewBufferString("")
|
||||||
tc.context.Output = out
|
tc.context.Output = out
|
||||||
err := DiffFormatWrite(tc.context, diffs)
|
err := diffFormatWrite(tc.context, diffs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.Error(t, err, tc.expected)
|
assert.Error(t, err, tc.expected)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ func (c *statsContext) Container() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *statsContext) Name() string {
|
func (c *statsContext) Name() string {
|
||||||
|
// TODO(thaJeztah): make this explicitly trim the "/" prefix, not just any char.
|
||||||
if len(c.s.Name) > 1 {
|
if len(c.s.Name) > 1 {
|
||||||
return c.s.Name[1:]
|
return c.s.Name[1:]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,45 +5,181 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/cli/internal/test"
|
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContainerStatsContext(t *testing.T) {
|
func TestContainerStatsContext(t *testing.T) {
|
||||||
containerID := test.RandomID()
|
const actorID = "c74518277ddc15a6afeaaeb06ee5f7433dcb27188224777c1efa7df1e8766d65"
|
||||||
|
|
||||||
var ctx statsContext
|
var ctx statsContext
|
||||||
tt := []struct {
|
tests := []struct {
|
||||||
|
name string
|
||||||
stats StatsEntry
|
stats StatsEntry
|
||||||
osType string
|
osType string
|
||||||
expValue string
|
expValue string
|
||||||
expHeader string
|
expHeader string
|
||||||
call func() string
|
call func() string
|
||||||
}{
|
}{
|
||||||
{StatsEntry{Container: containerID}, "", containerID, containerHeader, ctx.Container},
|
{
|
||||||
{StatsEntry{CPUPercentage: 5.5}, "", "5.50%", cpuPercHeader, ctx.CPUPerc},
|
name: "Container id",
|
||||||
{StatsEntry{CPUPercentage: 5.5, IsInvalid: true}, "", "--", cpuPercHeader, ctx.CPUPerc},
|
stats: StatsEntry{ID: actorID, Container: actorID},
|
||||||
{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3}, "", "0.31B / 12.3B", netIOHeader, ctx.NetIO},
|
expValue: actorID,
|
||||||
{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, "", "--", netIOHeader, ctx.NetIO},
|
expHeader: containerHeader,
|
||||||
{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, "", "0.1B / 2.3B", blockIOHeader, ctx.BlockIO},
|
call: ctx.Container,
|
||||||
{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},
|
name: "Container name",
|
||||||
{StatsEntry{MemoryPercentage: 10.2}, "windows", "--", memPercHeader, ctx.MemPerc},
|
stats: StatsEntry{ID: actorID, Container: "a-long-container-name"},
|
||||||
{StatsEntry{Memory: 24, MemoryLimit: 30}, "", "24B / 30B", memUseHeader, ctx.MemUsage},
|
expValue: "a-long-container-name",
|
||||||
{StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, "", "-- / --", memUseHeader, ctx.MemUsage},
|
expHeader: containerHeader,
|
||||||
{StatsEntry{Memory: 24, MemoryLimit: 30}, "windows", "24B", winMemUseHeader, ctx.MemUsage},
|
call: ctx.Container,
|
||||||
{StatsEntry{PidsCurrent: 10}, "", "10", pidsHeader, ctx.PIDs},
|
},
|
||||||
{StatsEntry{PidsCurrent: 10, IsInvalid: true}, "", "--", pidsHeader, ctx.PIDs},
|
{
|
||||||
{StatsEntry{PidsCurrent: 10}, "windows", "--", pidsHeader, ctx.PIDs},
|
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 {
|
for _, tc := range tests {
|
||||||
ctx = statsContext{s: te.stats, os: te.osType}
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
if v := te.call(); v != te.expValue {
|
ctx = statsContext{s: tc.stats, os: tc.osType}
|
||||||
t.Fatalf("Expected %q, got %q", te.expValue, v)
|
if v := tc.call(); v != tc.expValue {
|
||||||
}
|
t.Fatalf("Expected %q, got %q", tc.expValue, v)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ type killOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewKillCommand creates a new cobra.Command for `docker kill`
|
// NewKillCommand creates a new cobra.Command for `docker kill`
|
||||||
func NewKillCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewKillCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newKillCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newKillCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts killOptions
|
var opts killOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -27,12 +33,12 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Args: cli.RequiresMinArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.containers = args
|
opts.containers = args
|
||||||
return runKill(cmd.Context(), dockerCli, &opts)
|
return runKill(cmd.Context(), dockerCLI, &opts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container kill, docker kill",
|
"aliases": "docker container kill, docker kill",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func TestRunKill(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd := NewKillCommand(cli)
|
cmd := newKillCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
|
|
||||||
cmd.SetArgs([]string{
|
cmd.SetArgs([]string{
|
||||||
@@ -56,7 +56,7 @@ func TestRunKillClientError(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd := NewKillCommand(cli)
|
cmd := newKillCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"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/formatter"
|
||||||
flagsHelper "github.com/docker/cli/cli/flags"
|
flagsHelper "github.com/docker/cli/cli/flags"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
@@ -29,7 +28,13 @@ type psOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPsCommand creates a new cobra.Command for `docker ps`
|
// NewPsCommand creates a new cobra.Command for `docker ps`
|
||||||
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
|
func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newPsCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
options := psOptions{filter: opts.NewFilterOpt()}
|
options := psOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -44,7 +49,7 @@ func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
|
|||||||
"category-top": "3",
|
"category-top": "3",
|
||||||
"aliases": "docker container ls, docker container list, docker container ps, docker ps",
|
"aliases": "docker container ls, docker container list, docker container ps, docker ps",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.NoComplete,
|
ValidArgsFunction: cobra.NoFileCompletions,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
@@ -62,7 +67,7 @@ func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newListCommand(dockerCLI command.Cli) *cobra.Command {
|
func newListCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
cmd := *NewPsCommand(dockerCLI)
|
cmd := *newPsCommand(dockerCLI)
|
||||||
cmd.Aliases = []string{"ps", "list"}
|
cmd.Aliases = []string{"ps", "list"}
|
||||||
cmd.Use = "ls [OPTIONS]"
|
cmd.Use = "ls [OPTIONS]"
|
||||||
return &cmd
|
return &cmd
|
||||||
@@ -82,7 +87,7 @@ func buildContainerListOptions(options *psOptions) (*container.ListOptions, erro
|
|||||||
|
|
||||||
// always validate template when `--format` is used, for consistency
|
// always validate template when `--format` is used, for consistency
|
||||||
if len(options.format) > 0 {
|
if len(options.format) > 0 {
|
||||||
tmpl, err := templates.NewParse("", options.format)
|
tmpl, err := templates.Parse(options.format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to parse template")
|
return nil, errors.Wrap(err, "failed to parse template")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,13 @@ type logsOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewLogsCommand creates a new cobra.Command for `docker logs`
|
// NewLogsCommand creates a new cobra.Command for `docker logs`
|
||||||
func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewLogsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newLogsCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts logsOptions
|
var opts logsOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -33,12 +39,12 @@ func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.container = args[0]
|
opts.container = args[0]
|
||||||
return runLogs(cmd.Context(), dockerCli, &opts)
|
return runLogs(cmd.Context(), dockerCLI, &opts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container logs, docker logs",
|
"aliases": "docker container logs, docker logs",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, true),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -12,8 +13,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/compose/loader"
|
|
||||||
"github.com/docker/cli/internal/lazyregexp"
|
"github.com/docker/cli/internal/lazyregexp"
|
||||||
|
"github.com/docker/cli/internal/volumespec"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
mounttypes "github.com/docker/docker/api/types/mount"
|
||||||
@@ -141,6 +142,16 @@ type containerOptions struct {
|
|||||||
Args []string
|
Args []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addPlatformFlag adds "--platform" to a set of flags for API version 1.32 and
|
||||||
|
// later, using the value of "DOCKER_DEFAULT_PLATFORM" (if set) as a default.
|
||||||
|
//
|
||||||
|
// It should not be used for new uses, which may have a different API version
|
||||||
|
// requirement.
|
||||||
|
func addPlatformFlag(flags *pflag.FlagSet, target *string) {
|
||||||
|
flags.StringVar(target, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
|
||||||
|
_ = flags.SetAnnotation("platform", "version", []string{"1.32"})
|
||||||
|
}
|
||||||
|
|
||||||
// addFlags adds all command line flags that will be used by parse to the FlagSet
|
// addFlags adds all command line flags that will be used by parse to the FlagSet
|
||||||
func addFlags(flags *pflag.FlagSet) *containerOptions {
|
func addFlags(flags *pflag.FlagSet) *containerOptions {
|
||||||
copts := &containerOptions{
|
copts := &containerOptions{
|
||||||
@@ -340,7 +351,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
|||||||
|
|
||||||
// Validate the input mac address
|
// Validate the input mac address
|
||||||
if copts.macAddress != "" {
|
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)
|
return nil, errors.Errorf("%s is not a valid mac address", copts.macAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,7 +375,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
|||||||
volumes := copts.volumes.GetMap()
|
volumes := copts.volumes.GetMap()
|
||||||
// add any bind targets to the list of container volumes
|
// add any bind targets to the list of container volumes
|
||||||
for bind := range copts.volumes.GetMap() {
|
for bind := range copts.volumes.GetMap() {
|
||||||
parsed, err := loader.ParseVolume(bind)
|
parsed, err := volumespec.Parse(bind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -873,7 +884,7 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ep.MacAddress != "" {
|
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)
|
return nil, errors.Errorf("%s is not a valid mac address", ep.MacAddress)
|
||||||
}
|
}
|
||||||
epConfig.MacAddress = ep.MacAddress
|
epConfig.MacAddress = ep.MacAddress
|
||||||
|
|||||||
@@ -17,7 +17,13 @@ type pauseOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPauseCommand creates a new cobra.Command for `docker pause`
|
// NewPauseCommand creates a new cobra.Command for `docker pause`
|
||||||
func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewPauseCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newPauseCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPauseCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts pauseOptions
|
var opts pauseOptions
|
||||||
|
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
@@ -26,12 +32,12 @@ func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Args: cli.RequiresMinArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.containers = args
|
opts.containers = args
|
||||||
return runPause(cmd.Context(), dockerCli, &opts)
|
return runPause(cmd.Context(), dockerCLI, &opts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container pause, docker pause",
|
"aliases": "docker container pause, docker pause",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(ctr container.Summary) bool {
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, false, func(ctr container.Summary) bool {
|
||||||
return ctr.State != container.StatePaused
|
return ctr.State != container.StatePaused
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func TestRunPause(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd := NewPauseCommand(cli)
|
cmd := newPauseCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetArgs([]string{"container-id-1", "container-id-2"})
|
cmd.SetArgs([]string{"container-id-1", "container-id-2"})
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ func TestRunPauseClientError(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd := NewPauseCommand(cli)
|
cmd := newPauseCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs([]string{"container-id-1", "container-id-2"})
|
cmd.SetArgs([]string{"container-id-1", "container-id-2"})
|
||||||
|
|||||||
@@ -24,7 +24,13 @@ type portOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPortCommand creates a new cobra.Command for `docker port`
|
// NewPortCommand creates a new cobra.Command for `docker port`
|
||||||
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
func NewPortCommand(dockerCli command.Cli) *cobra.Command {
|
func NewPortCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
return newPortCommand(dockerCli)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPortCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var opts portOptions
|
var opts portOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func TestNewPortCommandOutput(t *testing.T) {
|
|||||||
return ci, nil
|
return ci, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := NewPortCommand(cli)
|
cmd := newPortCommand(cli)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs([]string{"some_container", tc.port})
|
cmd.SetArgs([]string{"some_container", tc.port})
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/completion"
|
|
||||||
"github.com/docker/cli/internal/prompt"
|
"github.com/docker/cli/internal/prompt"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
@@ -20,7 +19,13 @@ type pruneOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPruneCommand returns a new cobra prune command for containers
|
// NewPruneCommand returns a new cobra prune command for containers
|
||||||
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewPruneCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newPruneCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPruneCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
options := pruneOptions{filter: opts.NewFilterOpt()}
|
options := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -28,18 +33,18 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Short: "Remove all stopped containers",
|
Short: "Remove all stopped containers",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCli, options)
|
spaceReclaimed, output, err := runPrune(cmd.Context(), dockerCLI, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if output != "" {
|
if output != "" {
|
||||||
fmt.Fprintln(dockerCli.Out(), output)
|
fmt.Fprintln(dockerCLI.Out(), output)
|
||||||
}
|
}
|
||||||
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
|
fmt.Fprintln(dockerCLI.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{"version": "1.25"},
|
Annotations: map[string]string{"version": "1.25"},
|
||||||
ValidArgsFunction: completion.NoComplete,
|
ValidArgsFunction: cobra.NoFileCompletions,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func TestContainerPrunePromptTermination(t *testing.T) {
|
|||||||
return container.PruneReport{}, errors.New("fakeClient containerPruneFunc should not be called")
|
return container.PruneReport{}, errors.New("fakeClient containerPruneFunc should not be called")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
cmd := NewPruneCommand(cli)
|
cmd := newPruneCommand(cli)
|
||||||
cmd.SetArgs([]string{})
|
cmd.SetArgs([]string{})
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ type renameOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewRenameCommand creates a new cobra.Command for `docker rename`
|
// NewRenameCommand creates a new cobra.Command for `docker rename`
|
||||||
func NewRenameCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewRenameCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newRenameCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRenameCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts renameOptions
|
var opts renameOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -28,12 +34,12 @@ func NewRenameCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.oldName = args[0]
|
opts.oldName = args[0]
|
||||||
opts.newName = args[1]
|
opts.newName = args[1]
|
||||||
return runRename(cmd.Context(), dockerCli, &opts)
|
return runRename(cmd.Context(), dockerCLI, &opts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container rename, docker rename",
|
"aliases": "docker container rename, docker rename",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, true),
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func TestRunRename(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd := NewRenameCommand(cli)
|
cmd := newRenameCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs([]string{tc.oldName, tc.newName})
|
cmd.SetArgs([]string{tc.oldName, tc.newName})
|
||||||
@@ -66,7 +66,7 @@ func TestRunRenameClientError(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd := NewRenameCommand(cli)
|
cmd := newRenameCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs([]string{"oldName", "newName"})
|
cmd.SetArgs([]string{"oldName", "newName"})
|
||||||
|
|||||||
@@ -21,7 +21,13 @@ type restartOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewRestartCommand creates a new cobra.Command for `docker restart`
|
// NewRestartCommand creates a new cobra.Command for `docker restart`
|
||||||
func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewRestartCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newRestartCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRestartCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts restartOptions
|
var opts restartOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -34,12 +40,12 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
}
|
}
|
||||||
opts.containers = args
|
opts.containers = args
|
||||||
opts.timeoutChanged = cmd.Flags().Changed("timeout") || cmd.Flags().Changed("time")
|
opts.timeoutChanged = cmd.Flags().Changed("timeout") || cmd.Flags().Changed("time")
|
||||||
return runRestart(cmd.Context(), dockerCli, &opts)
|
return runRestart(cmd.Context(), dockerCLI, &opts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container restart, docker restart",
|
"aliases": "docker container restart, docker restart",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, true),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ func TestRestart(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Version: "1.36",
|
Version: "1.36",
|
||||||
})
|
})
|
||||||
cmd := NewRestartCommand(cli)
|
cmd := newRestartCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs(tc.args)
|
cmd.SetArgs(tc.args)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
cerrdefs "github.com/containerd/errdefs"
|
"github.com/containerd/errdefs"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/completion"
|
"github.com/docker/cli/cli/command/completion"
|
||||||
@@ -23,7 +23,13 @@ type rmOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewRmCommand creates a new cobra.Command for `docker rm`
|
// NewRmCommand creates a new cobra.Command for `docker rm`
|
||||||
func NewRmCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewRmCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newRmCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRmCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts rmOptions
|
var opts rmOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -32,12 +38,12 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Args: cli.RequiresMinArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.containers = args
|
opts.containers = args
|
||||||
return runRm(cmd.Context(), dockerCli, &opts)
|
return runRm(cmd.Context(), dockerCLI, &opts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container rm, docker container remove, docker rm",
|
"aliases": "docker container rm, docker container remove, docker rm",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, true, func(ctr container.Summary) bool {
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, true, func(ctr container.Summary) bool {
|
||||||
return opts.force || ctr.State == container.StateExited || ctr.State == container.StateCreated
|
return opts.force || ctr.State == container.StateExited || ctr.State == container.StateCreated
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -53,7 +59,7 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
// top-level "docker rm", it also adds a "remove" alias to support
|
// top-level "docker rm", it also adds a "remove" alias to support
|
||||||
// "docker container remove" in addition to "docker container rm".
|
// "docker container remove" in addition to "docker container rm".
|
||||||
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
cmd := *NewRmCommand(dockerCli)
|
cmd := *newRmCommand(dockerCli)
|
||||||
cmd.Aliases = []string{"rm", "remove"}
|
cmd.Aliases = []string{"rm", "remove"}
|
||||||
return &cmd
|
return &cmd
|
||||||
}
|
}
|
||||||
@@ -75,7 +81,7 @@ func runRm(ctx context.Context, dockerCLI command.Cli, opts *rmOptions) error {
|
|||||||
var errs []error
|
var errs []error
|
||||||
for _, name := range opts.containers {
|
for _, name := range opts.containers {
|
||||||
if err := <-errChan; err != nil {
|
if err := <-errChan; err != nil {
|
||||||
if opts.force && cerrdefs.IsNotFound(err) {
|
if opts.force && errdefs.IsNotFound(err) {
|
||||||
_, _ = fmt.Fprintln(dockerCLI.Err(), err)
|
_, _ = fmt.Fprintln(dockerCLI.Err(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func TestRemoveForce(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Version: "1.36",
|
Version: "1.36",
|
||||||
})
|
})
|
||||||
cmd := NewRmCommand(cli)
|
cmd := newRmCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs(tc.args)
|
cmd.SetArgs(tc.args)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/completion"
|
"github.com/docker/cli/cli/command/completion"
|
||||||
|
"github.com/docker/cli/cli/trust"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/moby/sys/signal"
|
"github.com/moby/sys/signal"
|
||||||
@@ -28,7 +29,13 @@ type runOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewRunCommand create a new `docker run` command
|
// NewRunCommand create a new `docker run` command
|
||||||
func NewRunCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewRunCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newRunCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRunCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var options runOptions
|
var options runOptions
|
||||||
var copts *containerOptions
|
var copts *containerOptions
|
||||||
|
|
||||||
@@ -41,9 +48,9 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
copts.Args = args[1:]
|
copts.Args = args[1:]
|
||||||
}
|
}
|
||||||
return runRun(cmd.Context(), dockerCli, cmd.Flags(), &options, copts)
|
return runRun(cmd.Context(), dockerCLI, cmd.Flags(), &options, copts)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ImageNames(dockerCli, 1),
|
ValidArgsFunction: completion.ImageNames(dockerCLI, 1),
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"category-top": "1",
|
"category-top": "1",
|
||||||
"aliases": "docker container run, docker run",
|
"aliases": "docker container run, docker run",
|
||||||
@@ -66,18 +73,19 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
// with hostname
|
// with hostname
|
||||||
flags.Bool("help", false, "Print usage")
|
flags.Bool("help", false, "Print usage")
|
||||||
|
|
||||||
command.AddPlatformFlag(flags, &options.platform)
|
// TODO(thaJeztah): consider adding platform as "image create option" on containerOptions
|
||||||
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
addPlatformFlag(flags, &options.platform)
|
||||||
|
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
|
||||||
copts = addFlags(flags)
|
copts = addFlags(flags)
|
||||||
|
|
||||||
_ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys)
|
_ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys)
|
||||||
addCompletions(cmd, dockerCli)
|
addCompletions(cmd, dockerCLI)
|
||||||
|
|
||||||
flags.VisitAll(func(flag *pflag.Flag) {
|
flags.VisitAll(func(flag *pflag.Flag) {
|
||||||
// Set a default completion function if none was set. We don't look
|
// 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
|
// up if it does already have one set, because Cobra does this for
|
||||||
// us, and returns an error (which we ignore for this reason).
|
// 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
|
return cmd
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -20,7 +18,8 @@ import (
|
|||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/progress"
|
||||||
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
@@ -40,7 +39,7 @@ func TestRunValidateFlags(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cmd := NewRunCommand(test.NewFakeCli(&fakeClient{}))
|
cmd := newRunCommand(test.NewFakeCli(&fakeClient{}))
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs(tc.args)
|
cmd.SetArgs(tc.args)
|
||||||
@@ -64,7 +63,7 @@ func TestRunLabel(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Version: "1.36",
|
Version: "1.36",
|
||||||
})
|
})
|
||||||
cmd := NewRunCommand(fakeCLI)
|
cmd := newRunCommand(fakeCLI)
|
||||||
cmd.SetArgs([]string{"--detach=true", "--label", "foo", "busybox"})
|
cmd.SetArgs([]string{"--detach=true", "--label", "foo", "busybox"})
|
||||||
assert.NilError(t, cmd.Execute())
|
assert.NilError(t, cmd.Execute())
|
||||||
}
|
}
|
||||||
@@ -111,7 +110,7 @@ func TestRunAttach(t *testing.T) {
|
|||||||
fc.SetIn(streams.NewIn(tty))
|
fc.SetIn(streams.NewIn(tty))
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd := NewRunCommand(fakeCLI)
|
cmd := newRunCommand(fakeCLI)
|
||||||
cmd.SetArgs([]string{"-it", "busybox"})
|
cmd.SetArgs([]string{"-it", "busybox"})
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
cmdErrC := make(chan error, 1)
|
cmdErrC := make(chan error, 1)
|
||||||
@@ -188,7 +187,7 @@ func TestRunAttachTermination(t *testing.T) {
|
|||||||
fc.SetIn(streams.NewIn(tty))
|
fc.SetIn(streams.NewIn(tty))
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd := NewRunCommand(fakeCLI)
|
cmd := newRunCommand(fakeCLI)
|
||||||
cmd.SetArgs([]string{"-it", "busybox"})
|
cmd.SetArgs([]string{"-it", "busybox"})
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
cmdErrC := make(chan error, 1)
|
cmdErrC := make(chan error, 1)
|
||||||
@@ -242,23 +241,19 @@ func TestRunPullTermination(t *testing.T) {
|
|||||||
_ = server.Close()
|
_ = server.Close()
|
||||||
})
|
})
|
||||||
go func() {
|
go func() {
|
||||||
enc := json.NewEncoder(server)
|
id := test.RandomID()[:12] // short-ID
|
||||||
|
progressOutput := streamformatter.NewJSONProgressOutput(server, true)
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
assert.NilError(t, server.Close(), "failed to close imageCreateFunc server")
|
assert.NilError(t, server.Close(), "failed to close imageCreateFunc server")
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
assert.NilError(t, enc.Encode(jsonmessage.JSONMessage{
|
assert.NilError(t, progressOutput.WriteProgress(progress.Progress{
|
||||||
Status: "Downloading",
|
ID: id,
|
||||||
ID: fmt.Sprintf("id-%d", i),
|
Message: "Downloading",
|
||||||
TimeNano: time.Now().UnixNano(),
|
Current: int64(i),
|
||||||
Time: time.Now().Unix(),
|
Total: 100,
|
||||||
Progress: &jsonmessage.JSONProgress{
|
|
||||||
Current: int64(i),
|
|
||||||
Total: 100,
|
|
||||||
Start: 0,
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
}
|
}
|
||||||
@@ -270,7 +265,7 @@ func TestRunPullTermination(t *testing.T) {
|
|||||||
Version: "1.30",
|
Version: "1.30",
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd := NewRunCommand(fakeCLI)
|
cmd := newRunCommand(fakeCLI)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs([]string{"--pull", "always", "foobar:latest"})
|
cmd.SetArgs([]string{"--pull", "always", "foobar:latest"})
|
||||||
@@ -328,6 +323,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Setenv("DOCKER_CONTENT_TRUST", "true")
|
||||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||||
createContainerFunc: func(config *container.Config,
|
createContainerFunc: func(config *container.Config,
|
||||||
hostConfig *container.HostConfig,
|
hostConfig *container.HostConfig,
|
||||||
@@ -337,9 +333,9 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
|||||||
) (container.CreateResponse, error) {
|
) (container.CreateResponse, error) {
|
||||||
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
|
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
|
||||||
},
|
},
|
||||||
}, test.EnableContentTrust)
|
})
|
||||||
fakeCLI.SetNotaryClient(tc.notaryFunc)
|
fakeCLI.SetNotaryClient(tc.notaryFunc)
|
||||||
cmd := NewRunCommand(fakeCLI)
|
cmd := newRunCommand(fakeCLI)
|
||||||
cmd.SetArgs(tc.args)
|
cmd.SetArgs(tc.args)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
|
|||||||
@@ -28,7 +28,13 @@ type StartOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewStartCommand creates a new cobra.Command for `docker start`
|
// NewStartCommand creates a new cobra.Command for `docker start`
|
||||||
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
func NewStartCommand(dockerCli command.Cli) *cobra.Command {
|
func NewStartCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
return newStartCommand(dockerCli)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStartCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var opts StartOptions
|
var opts StartOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
|||||||
@@ -64,7 +64,13 @@ type StatsOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewStatsCommand creates a new [cobra.Command] for "docker stats".
|
// NewStatsCommand creates a new [cobra.Command] for "docker stats".
|
||||||
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
func NewStatsCommand(dockerCLI command.Cli) *cobra.Command {
|
func NewStatsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newStatsCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStatsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
options := StatsOptions{}
|
options := StatsOptions{}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -133,7 +139,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
|||||||
eh := newEventHandler()
|
eh := newEventHandler()
|
||||||
if options.All {
|
if options.All {
|
||||||
eh.setHandler(events.ActionCreate, func(e events.Message) {
|
eh.setHandler(events.ActionCreate, func(e events.Message) {
|
||||||
s := NewStats(e.Actor.ID[:12])
|
s := NewStats(e.Actor.ID)
|
||||||
if cStats.add(s) {
|
if cStats.add(s) {
|
||||||
waitFirst.Add(1)
|
waitFirst.Add(1)
|
||||||
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
|
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
|
||||||
@@ -142,7 +148,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
|||||||
}
|
}
|
||||||
|
|
||||||
eh.setHandler(events.ActionStart, func(e events.Message) {
|
eh.setHandler(events.ActionStart, func(e events.Message) {
|
||||||
s := NewStats(e.Actor.ID[:12])
|
s := NewStats(e.Actor.ID)
|
||||||
if cStats.add(s) {
|
if cStats.add(s) {
|
||||||
waitFirst.Add(1)
|
waitFirst.Add(1)
|
||||||
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
|
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
|
||||||
@@ -151,7 +157,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
|||||||
|
|
||||||
if !options.All {
|
if !options.All {
|
||||||
eh.setHandler(events.ActionDie, func(e events.Message) {
|
eh.setHandler(events.ActionDie, func(e events.Message) {
|
||||||
cStats.remove(e.Actor.ID[:12])
|
cStats.remove(e.Actor.ID)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +210,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, ctr := range cs {
|
for _, ctr := range cs {
|
||||||
s := NewStats(ctr.ID[:12])
|
s := NewStats(ctr.ID)
|
||||||
if cStats.add(s) {
|
if cStats.add(s) {
|
||||||
waitFirst.Add(1)
|
waitFirst.Add(1)
|
||||||
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
|
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
|
||||||
@@ -357,7 +363,12 @@ func (eh *eventHandler) watch(c <-chan events.Message) {
|
|||||||
if !exists {
|
if !exists {
|
||||||
continue
|
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)
|
go h(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,13 @@ type stopOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewStopCommand creates a new cobra.Command for `docker stop`
|
// NewStopCommand creates a new cobra.Command for `docker stop`
|
||||||
func NewStopCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewStopCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newStopCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStopCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts stopOptions
|
var opts stopOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -34,12 +40,12 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
}
|
}
|
||||||
opts.containers = args
|
opts.containers = args
|
||||||
opts.timeoutChanged = cmd.Flags().Changed("timeout") || cmd.Flags().Changed("time")
|
opts.timeoutChanged = cmd.Flags().Changed("timeout") || cmd.Flags().Changed("time")
|
||||||
return runStop(cmd.Context(), dockerCli, &opts)
|
return runStop(cmd.Context(), dockerCLI, &opts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container stop, docker stop",
|
"aliases": "docker container stop, docker stop",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func TestStop(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Version: "1.36",
|
Version: "1.36",
|
||||||
})
|
})
|
||||||
cmd := NewStopCommand(cli)
|
cmd := newStopCommand(cli)
|
||||||
cmd.SetOut(io.Discard)
|
cmd.SetOut(io.Discard)
|
||||||
cmd.SetErr(io.Discard)
|
cmd.SetErr(io.Discard)
|
||||||
cmd.SetArgs(tc.args)
|
cmd.SetArgs(tc.args)
|
||||||
|
|||||||
@@ -19,7 +19,13 @@ type topOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTopCommand creates a new cobra.Command for `docker top`
|
// NewTopCommand creates a new cobra.Command for `docker top`
|
||||||
func NewTopCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewTopCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newTopCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTopCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts topOptions
|
var opts topOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -29,12 +35,12 @@ func NewTopCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.container = args[0]
|
opts.container = args[0]
|
||||||
opts.args = args[1:]
|
opts.args = args[1:]
|
||||||
return runTop(cmd.Context(), dockerCli, &opts)
|
return runTop(cmd.Context(), dockerCLI, &opts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container top, docker top",
|
"aliases": "docker container top, docker top",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|||||||
@@ -17,7 +17,13 @@ type unpauseOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewUnpauseCommand creates a new cobra.Command for `docker unpause`
|
// NewUnpauseCommand creates a new cobra.Command for `docker unpause`
|
||||||
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
func NewUnpauseCommand(dockerCli command.Cli) *cobra.Command {
|
func NewUnpauseCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
return newUnpauseCommand(dockerCli)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnpauseCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var opts unpauseOptions
|
var opts unpauseOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
|||||||
@@ -37,7 +37,13 @@ type updateOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewUpdateCommand creates a new cobra.Command for `docker update`
|
// NewUpdateCommand creates a new cobra.Command for `docker update`
|
||||||
func NewUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewUpdateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newUpdateCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUpdateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var options updateOptions
|
var options updateOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -47,12 +53,12 @@ func NewUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.containers = args
|
options.containers = args
|
||||||
options.nFlag = cmd.Flags().NFlag()
|
options.nFlag = cmd.Flags().NFlag()
|
||||||
return runUpdate(cmd.Context(), dockerCli, &options)
|
return runUpdate(cmd.Context(), dockerCLI, &options)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container update, docker update",
|
"aliases": "docker container update, docker update",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, true),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|||||||
@@ -16,7 +16,13 @@ type waitOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewWaitCommand creates a new cobra.Command for `docker wait`
|
// NewWaitCommand creates a new cobra.Command for `docker wait`
|
||||||
func NewWaitCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewWaitCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newWaitCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWaitCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts waitOptions
|
var opts waitOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -25,12 +31,12 @@ func NewWaitCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Args: cli.RequiresMinArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.containers = args
|
opts.containers = args
|
||||||
return runWait(cmd.Context(), dockerCli, &opts)
|
return runWait(cmd.Context(), dockerCLI, &opts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container wait, docker wait",
|
"aliases": "docker container wait, docker wait",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
+18
-11
@@ -7,23 +7,30 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewContextCommand returns the context cli subcommand
|
// NewContextCommand returns the context cli subcommand
|
||||||
func NewContextCommand(dockerCli command.Cli) *cobra.Command {
|
//
|
||||||
|
// Deprecated: Do not import commands directly. They will be removed in a future release.
|
||||||
|
func NewContextCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
|
return newContextCommand(dockerCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newContextCommand returns the context cli subcommand
|
||||||
|
func newContextCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "context",
|
Use: "context",
|
||||||
Short: "Manage contexts",
|
Short: "Manage contexts",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: command.ShowHelp(dockerCli.Err()),
|
RunE: command.ShowHelp(dockerCLI.Err()),
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
newCreateCommand(dockerCli),
|
newCreateCommand(dockerCLI),
|
||||||
newListCommand(dockerCli),
|
newListCommand(dockerCLI),
|
||||||
newUseCommand(dockerCli),
|
newUseCommand(dockerCLI),
|
||||||
newExportCommand(dockerCli),
|
newExportCommand(dockerCLI),
|
||||||
newImportCommand(dockerCli),
|
newImportCommand(dockerCLI),
|
||||||
newRemoveCommand(dockerCli),
|
newRemoveCommand(dockerCLI),
|
||||||
newUpdateCommand(dockerCli),
|
newUpdateCommand(dockerCLI),
|
||||||
newInspectCommand(dockerCli),
|
newInspectCommand(dockerCLI),
|
||||||
newShowCommand(dockerCli),
|
newShowCommand(dockerCLI),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
cerrdefs "github.com/containerd/errdefs"
|
"github.com/containerd/errdefs"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"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/command/formatter/tabwriter"
|
||||||
"github.com/docker/cli/cli/context/docker"
|
"github.com/docker/cli/cli/context/docker"
|
||||||
"github.com/docker/cli/cli/context/store"
|
"github.com/docker/cli/cli/context/store"
|
||||||
@@ -19,6 +18,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// CreateOptions are the options used for creating a context
|
// 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 {
|
type CreateOptions struct {
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
@@ -30,6 +31,18 @@ type CreateOptions struct {
|
|||||||
metaData map[string]any
|
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 {
|
func longCreateDescription() string {
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
buf.WriteString("Create a context\n\nDocker endpoint config:\n\n")
|
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 {
|
func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
opts := &CreateOptions{}
|
opts := createOptions{}
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "create [OPTIONS] CONTEXT",
|
Use: "create [OPTIONS] CONTEXT",
|
||||||
Short: "Create a context",
|
Short: "Create a context",
|
||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.Name = args[0]
|
opts.name = args[0]
|
||||||
return RunCreate(dockerCLI, opts)
|
return runCreate(dockerCLI, &opts)
|
||||||
},
|
},
|
||||||
Long: longCreateDescription(),
|
Long: longCreateDescription(),
|
||||||
ValidArgsFunction: completion.NoComplete,
|
ValidArgsFunction: cobra.NoFileCompletions,
|
||||||
}
|
}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVar(&opts.Description, "description", "", "Description of the context")
|
flags.StringVar(&opts.description, "description", "", "Description of the context")
|
||||||
flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint")
|
flags.StringToStringVar(&opts.endpoint, "docker", nil, "set the docker endpoint")
|
||||||
flags.StringVar(&opts.From, "from", "", "create context from a named context")
|
flags.StringVar(&opts.from, "from", "", "create context from a named context")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunCreate creates a Docker context
|
// 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 {
|
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()
|
s := dockerCLI.ContextStore()
|
||||||
err := checkContextNameForCreation(s, o.Name)
|
err := checkContextNameForCreation(s, opts.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case o.From == "" && o.Docker == nil:
|
case opts.from == "" && opts.endpoint == nil:
|
||||||
err = createFromExistingContext(s, dockerCLI.CurrentContext(), o)
|
err = createFromExistingContext(s, dockerCLI.CurrentContext(), opts)
|
||||||
case o.From != "":
|
case opts.from != "":
|
||||||
err = createFromExistingContext(s, o.From, o)
|
err = createFromExistingContext(s, opts.from, opts)
|
||||||
default:
|
default:
|
||||||
err = createNewContext(s, o)
|
err = createNewContext(s, opts)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_, _ = fmt.Fprintln(dockerCLI.Out(), o.Name)
|
_, _ = fmt.Fprintln(dockerCLI.Out(), opts.name)
|
||||||
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully created context %q\n", o.Name)
|
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully created context %q\n", opts.name)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
|
func createNewContext(contextStore store.ReaderWriter, opts *createOptions) error {
|
||||||
if o.Docker == nil {
|
if opts.endpoint == nil {
|
||||||
return errors.New("docker endpoint configuration is required")
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create docker endpoint config: %w", err)
|
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,
|
docker.DockerEndpoint: dockerEP,
|
||||||
},
|
},
|
||||||
Metadata: command.DockerContext{
|
Metadata: command.DockerContext{
|
||||||
Description: o.Description,
|
Description: opts.description,
|
||||||
AdditionalFields: o.metaData,
|
AdditionalFields: opts.metaData,
|
||||||
},
|
},
|
||||||
Name: o.Name,
|
Name: opts.name,
|
||||||
}
|
}
|
||||||
contextTLSData := store.ContextTLSData{}
|
contextTLSData := store.ContextTLSData{}
|
||||||
if dockerTLS != nil {
|
if dockerTLS != nil {
|
||||||
@@ -115,14 +144,14 @@ func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error {
|
|||||||
if err := contextStore.CreateOrUpdate(contextMetadata); err != nil {
|
if err := contextStore.CreateOrUpdate(contextMetadata); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return contextStore.ResetTLSMaterial(o.Name, &contextTLSData)
|
return contextStore.ResetTLSMaterial(opts.name, &contextTLSData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkContextNameForCreation(s store.Reader, name string) error {
|
func checkContextNameForCreation(s store.Reader, name string) error {
|
||||||
if err := store.ValidateContextName(name); err != nil {
|
if err := store.ValidateContextName(name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := s.GetMetadata(name); !cerrdefs.IsNotFound(err) {
|
if _, err := s.GetMetadata(name); !errdefs.IsNotFound(err) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error while getting existing contexts: %w", err)
|
return fmt.Errorf("error while getting existing contexts: %w", err)
|
||||||
}
|
}
|
||||||
@@ -131,16 +160,16 @@ func checkContextNameForCreation(s store.Reader, name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFromExistingContext(s store.ReaderWriter, fromContextName string, o *CreateOptions) error {
|
func createFromExistingContext(s store.ReaderWriter, fromContextName string, opts *createOptions) error {
|
||||||
if len(o.Docker) != 0 {
|
if len(opts.endpoint) != 0 {
|
||||||
return errors.New("cannot use --docker flag when --from is set")
|
return errors.New("cannot use --docker flag when --from is set")
|
||||||
}
|
}
|
||||||
reader := store.Export(fromContextName, &descriptionDecorator{
|
reader := store.Export(fromContextName, &descriptionDecorator{
|
||||||
Reader: s,
|
Reader: s,
|
||||||
description: o.Description,
|
description: opts.description,
|
||||||
})
|
})
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
return store.Import(o.Name, s, reader)
|
return store.Import(opts.name, s, reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
type descriptionDecorator struct {
|
type descriptionDecorator struct {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func TestCreate(t *testing.T) {
|
|||||||
assert.NilError(t, cli.ContextStore().CreateOrUpdate(store.Metadata{Name: "existing-context"}))
|
assert.NilError(t, cli.ContextStore().CreateOrUpdate(store.Metadata{Name: "existing-context"}))
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
doc string
|
doc string
|
||||||
options CreateOptions
|
options createOptions
|
||||||
expecterErr string
|
expecterErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -69,30 +69,30 @@ func TestCreate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
doc: "reserved name",
|
doc: "reserved name",
|
||||||
options: CreateOptions{
|
options: createOptions{
|
||||||
Name: "default",
|
name: "default",
|
||||||
},
|
},
|
||||||
expecterErr: `"default" is a reserved context name`,
|
expecterErr: `"default" is a reserved context name`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
doc: "whitespace-only name",
|
doc: "whitespace-only name",
|
||||||
options: CreateOptions{
|
options: createOptions{
|
||||||
Name: " ",
|
name: " ",
|
||||||
},
|
},
|
||||||
expecterErr: `context name " " is invalid`,
|
expecterErr: `context name " " is invalid`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
doc: "existing context",
|
doc: "existing context",
|
||||||
options: CreateOptions{
|
options: createOptions{
|
||||||
Name: "existing-context",
|
name: "existing-context",
|
||||||
},
|
},
|
||||||
expecterErr: `context "existing-context" already exists`,
|
expecterErr: `context "existing-context" already exists`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
doc: "invalid docker host",
|
doc: "invalid docker host",
|
||||||
options: CreateOptions{
|
options: createOptions{
|
||||||
Name: "invalid-docker-host",
|
name: "invalid-docker-host",
|
||||||
Docker: map[string]string{
|
endpoint: map[string]string{
|
||||||
"host": "some///invalid/host",
|
"host": "some///invalid/host",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -100,27 +100,27 @@ func TestCreate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
doc: "ssh host with skip-tls-verify=false",
|
doc: "ssh host with skip-tls-verify=false",
|
||||||
options: CreateOptions{
|
options: createOptions{
|
||||||
Name: "skip-tls-verify-false",
|
name: "skip-tls-verify-false",
|
||||||
Docker: map[string]string{
|
endpoint: map[string]string{
|
||||||
"host": "ssh://example.com,skip-tls-verify=false",
|
"host": "ssh://example.com,skip-tls-verify=false",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
doc: "ssh host with skip-tls-verify=true",
|
doc: "ssh host with skip-tls-verify=true",
|
||||||
options: CreateOptions{
|
options: createOptions{
|
||||||
Name: "skip-tls-verify-true",
|
name: "skip-tls-verify-true",
|
||||||
Docker: map[string]string{
|
endpoint: map[string]string{
|
||||||
"host": "ssh://example.com,skip-tls-verify=true",
|
"host": "ssh://example.com,skip-tls-verify=true",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
doc: "ssh host with skip-tls-verify=INVALID",
|
doc: "ssh host with skip-tls-verify=INVALID",
|
||||||
options: CreateOptions{
|
options: createOptions{
|
||||||
Name: "skip-tls-verify-invalid",
|
name: "skip-tls-verify-invalid",
|
||||||
Docker: map[string]string{
|
endpoint: map[string]string{
|
||||||
"host": "ssh://example.com",
|
"host": "ssh://example.com",
|
||||||
"skip-tls-verify": "INVALID",
|
"skip-tls-verify": "INVALID",
|
||||||
},
|
},
|
||||||
@@ -129,9 +129,9 @@ func TestCreate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
doc: "unknown option",
|
doc: "unknown option",
|
||||||
options: CreateOptions{
|
options: createOptions{
|
||||||
Name: "unknown-option",
|
name: "unknown-option",
|
||||||
Docker: map[string]string{
|
endpoint: map[string]string{
|
||||||
"UNKNOWN": "value",
|
"UNKNOWN": "value",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -140,7 +140,7 @@ func TestCreate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.doc, func(t *testing.T) {
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
err := RunCreate(cli, &tc.options)
|
err := runCreate(cli, &tc.options)
|
||||||
if tc.expecterErr == "" {
|
if tc.expecterErr == "" {
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
} else {
|
} else {
|
||||||
@@ -159,9 +159,9 @@ func assertContextCreateLogging(t *testing.T, cli *test.FakeCli, n string) {
|
|||||||
func TestCreateOrchestratorEmpty(t *testing.T) {
|
func TestCreateOrchestratorEmpty(t *testing.T) {
|
||||||
cli := makeFakeCli(t)
|
cli := makeFakeCli(t)
|
||||||
|
|
||||||
err := RunCreate(cli, &CreateOptions{
|
err := runCreate(cli, &createOptions{
|
||||||
Name: "test",
|
name: "test",
|
||||||
Docker: map[string]string{},
|
endpoint: map[string]string{},
|
||||||
})
|
})
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assertContextCreateLogging(t, cli, "test")
|
assertContextCreateLogging(t, cli, "test")
|
||||||
@@ -187,20 +187,20 @@ func TestCreateFromContext(t *testing.T) {
|
|||||||
|
|
||||||
cli := makeFakeCli(t)
|
cli := makeFakeCli(t)
|
||||||
cli.ResetOutputBuffers()
|
cli.ResetOutputBuffers()
|
||||||
assert.NilError(t, RunCreate(cli, &CreateOptions{
|
assert.NilError(t, runCreate(cli, &createOptions{
|
||||||
Name: "original",
|
name: "original",
|
||||||
Description: "original description",
|
description: "original description",
|
||||||
Docker: map[string]string{
|
endpoint: map[string]string{
|
||||||
keyHost: "tcp://42.42.42.42:2375",
|
keyHost: "tcp://42.42.42.42:2375",
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
assertContextCreateLogging(t, cli, "original")
|
assertContextCreateLogging(t, cli, "original")
|
||||||
|
|
||||||
cli.ResetOutputBuffers()
|
cli.ResetOutputBuffers()
|
||||||
assert.NilError(t, RunCreate(cli, &CreateOptions{
|
assert.NilError(t, runCreate(cli, &createOptions{
|
||||||
Name: "dummy",
|
name: "dummy",
|
||||||
Description: "dummy description",
|
description: "dummy description",
|
||||||
Docker: map[string]string{
|
endpoint: map[string]string{
|
||||||
keyHost: "tcp://24.24.24.24:2375",
|
keyHost: "tcp://24.24.24.24:2375",
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
@@ -211,11 +211,11 @@ func TestCreateFromContext(t *testing.T) {
|
|||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli.ResetOutputBuffers()
|
cli.ResetOutputBuffers()
|
||||||
err := RunCreate(cli, &CreateOptions{
|
err := runCreate(cli, &createOptions{
|
||||||
From: "original",
|
from: "original",
|
||||||
Name: tc.name,
|
name: tc.name,
|
||||||
Description: tc.description,
|
description: tc.description,
|
||||||
Docker: tc.docker,
|
endpoint: tc.docker,
|
||||||
})
|
})
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assertContextCreateLogging(t, cli, tc.name)
|
assertContextCreateLogging(t, cli, tc.name)
|
||||||
@@ -251,10 +251,10 @@ func TestCreateFromCurrent(t *testing.T) {
|
|||||||
|
|
||||||
cli := makeFakeCli(t)
|
cli := makeFakeCli(t)
|
||||||
cli.ResetOutputBuffers()
|
cli.ResetOutputBuffers()
|
||||||
assert.NilError(t, RunCreate(cli, &CreateOptions{
|
assert.NilError(t, runCreate(cli, &createOptions{
|
||||||
Name: "original",
|
name: "original",
|
||||||
Description: "original description",
|
description: "original description",
|
||||||
Docker: map[string]string{
|
endpoint: map[string]string{
|
||||||
keyHost: "tcp://42.42.42.42:2375",
|
keyHost: "tcp://42.42.42.42:2375",
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
@@ -265,9 +265,9 @@ func TestCreateFromCurrent(t *testing.T) {
|
|||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
cli.ResetOutputBuffers()
|
cli.ResetOutputBuffers()
|
||||||
err := RunCreate(cli, &CreateOptions{
|
err := runCreate(cli, &createOptions{
|
||||||
Name: tc.name,
|
name: tc.name,
|
||||||
Description: tc.description,
|
description: tc.description,
|
||||||
})
|
})
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assertContextCreateLogging(t, cli, tc.name)
|
assertContextCreateLogging(t, cli, tc.name)
|
||||||
|
|||||||
@@ -21,14 +21,11 @@ func TestExportImportWithFile(t *testing.T) {
|
|||||||
"MyCustomMetadata": t.Name(),
|
"MyCustomMetadata": t.Name(),
|
||||||
})
|
})
|
||||||
cli.ErrBuffer().Reset()
|
cli.ErrBuffer().Reset()
|
||||||
assert.NilError(t, RunExport(cli, &ExportOptions{
|
assert.NilError(t, runExport(cli, "test", contextFile))
|
||||||
ContextName: "test",
|
|
||||||
Dest: contextFile,
|
|
||||||
}))
|
|
||||||
assert.Equal(t, cli.ErrBuffer().String(), fmt.Sprintf("Written file %q\n", contextFile))
|
assert.Equal(t, cli.ErrBuffer().String(), fmt.Sprintf("Written file %q\n", contextFile))
|
||||||
cli.OutBuffer().Reset()
|
cli.OutBuffer().Reset()
|
||||||
cli.ErrBuffer().Reset()
|
cli.ErrBuffer().Reset()
|
||||||
assert.NilError(t, RunImport(cli, "test2", contextFile))
|
assert.NilError(t, runImport(cli, "test2", contextFile))
|
||||||
context1, err := cli.ContextStore().GetMetadata("test")
|
context1, err := cli.ContextStore().GetMetadata("test")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
context2, err := cli.ContextStore().GetMetadata("test2")
|
context2, err := cli.ContextStore().GetMetadata("test2")
|
||||||
@@ -55,15 +52,12 @@ func TestExportImportPipe(t *testing.T) {
|
|||||||
})
|
})
|
||||||
cli.ErrBuffer().Reset()
|
cli.ErrBuffer().Reset()
|
||||||
cli.OutBuffer().Reset()
|
cli.OutBuffer().Reset()
|
||||||
assert.NilError(t, RunExport(cli, &ExportOptions{
|
assert.NilError(t, runExport(cli, "test", "-"))
|
||||||
ContextName: "test",
|
|
||||||
Dest: "-",
|
|
||||||
}))
|
|
||||||
assert.Equal(t, cli.ErrBuffer().String(), "")
|
assert.Equal(t, cli.ErrBuffer().String(), "")
|
||||||
cli.SetIn(streams.NewIn(io.NopCloser(bytes.NewBuffer(cli.OutBuffer().Bytes()))))
|
cli.SetIn(streams.NewIn(io.NopCloser(bytes.NewBuffer(cli.OutBuffer().Bytes()))))
|
||||||
cli.OutBuffer().Reset()
|
cli.OutBuffer().Reset()
|
||||||
cli.ErrBuffer().Reset()
|
cli.ErrBuffer().Reset()
|
||||||
assert.NilError(t, RunImport(cli, "test2", "-"))
|
assert.NilError(t, runImport(cli, "test2", "-"))
|
||||||
context1, err := cli.ContextStore().GetMetadata("test")
|
context1, err := cli.ContextStore().GetMetadata("test")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
context2, err := cli.ContextStore().GetMetadata("test2")
|
context2, err := cli.ContextStore().GetMetadata("test2")
|
||||||
@@ -88,6 +82,6 @@ func TestExportExistingFile(t *testing.T) {
|
|||||||
cli := makeFakeCli(t)
|
cli := makeFakeCli(t)
|
||||||
cli.ErrBuffer().Reset()
|
cli.ErrBuffer().Reset()
|
||||||
assert.NilError(t, os.WriteFile(contextFile, []byte{}, 0o644))
|
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))
|
assert.Assert(t, os.IsExist(err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ExportOptions are the options used for exporting a context
|
// 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 {
|
type ExportOptions struct {
|
||||||
ContextName string
|
ContextName string
|
||||||
Dest 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.",
|
Short: "Export a context to a tar archive FILE or a tar stream on STDOUT.",
|
||||||
Args: cli.RequiresRangeArgs(1, 2),
|
Args: cli.RequiresRangeArgs(1, 2),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts := &ExportOptions{
|
contextName := args[0]
|
||||||
ContextName: args[0],
|
var dest string
|
||||||
}
|
|
||||||
if len(args) == 2 {
|
if len(args) == 2 {
|
||||||
opts.Dest = args[1]
|
dest = args[1]
|
||||||
} else {
|
} else {
|
||||||
opts.Dest = opts.ContextName + ".dockercontext"
|
dest = contextName + ".dockercontext"
|
||||||
}
|
}
|
||||||
return RunExport(dockerCLI, opts)
|
return runExport(dockerCLI, contextName, dest)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completeContextNames(dockerCLI, 1, true),
|
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
|
// 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 {
|
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
|
return err
|
||||||
}
|
}
|
||||||
reader := store.Export(opts.ContextName, dockerCli.ContextStore())
|
reader := store.Export(contextName, dockerCLI.ContextStore())
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
return writeTo(dockerCli, reader, opts.Dest)
|
return writeTo(dockerCLI, reader, dest)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func newImportCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Short: "Import a context from a tar or zip file",
|
Short: "Import a context from a tar or zip file",
|
||||||
Args: cli.ExactArgs(2),
|
Args: cli.ExactArgs(2),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
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 "-"
|
// TODO(thaJeztah): this should also include "-"
|
||||||
ValidArgsFunction: completion.FileNames,
|
ValidArgsFunction: completion.FileNames,
|
||||||
@@ -27,14 +27,21 @@ func newImportCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunImport imports a Docker context
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
if source == "-" {
|
if source == "-" {
|
||||||
reader = dockerCli.In()
|
reader = dockerCLI.In()
|
||||||
} else {
|
} else {
|
||||||
f, err := os.Open(source)
|
f, err := os.Open(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -44,11 +51,11 @@ func RunImport(dockerCli command.Cli, name string, source string) error {
|
|||||||
reader = f
|
reader = f
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := store.Import(name, dockerCli.ContextStore(), reader); err != nil {
|
if err := store.Import(name, dockerCLI.ContextStore(), reader); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = fmt.Fprintln(dockerCli.Out(), name)
|
_, _ = fmt.Fprintln(dockerCLI.Out(), name)
|
||||||
_, _ = fmt.Fprintf(dockerCli.Err(), "Successfully imported context %q\n", name)
|
_, _ = fmt.Fprintf(dockerCLI.Err(), "Successfully imported context %q\n", name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"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/formatter"
|
||||||
"github.com/docker/cli/cli/context/docker"
|
"github.com/docker/cli/cli/context/docker"
|
||||||
flagsHelper "github.com/docker/cli/cli/flags"
|
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 {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runList(dockerCli, opts)
|
return runList(dockerCli, opts)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.NoComplete,
|
ValidArgsFunction: cobra.NoFileCompletions,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user