mirror of https://github.com/docker/cli.git
Compare commits
455 Commits
Author | SHA1 | Date |
---|---|---|
|
27b316fc0d | |
|
f64b8a332d | |
|
06ed23d5fe | |
|
5bf3c6793d | |
|
5bce5e17af | |
|
0640306406 | |
|
ab7018b590 | |
|
2351f5b915 | |
|
153bd95158 | |
|
ce72a5c28b | |
|
d54c7f9e63 | |
|
6ec32660e9 | |
|
9e52a2817c | |
|
18cdc25bb4 | |
|
6fa7d18320 | |
|
89874983c8 | |
|
88f68273fd | |
|
a2c886251c | |
|
9a6cbbc586 | |
|
70915196cb | |
|
9477941c20 | |
|
6647e229be | |
|
0adaf6be3b | |
|
ba21666654 | |
|
321100e38b | |
|
5dd52a9efa | |
|
2827d037ba | |
|
95eeafa551 | |
|
81ea282e00 | |
|
0155c264ae | |
|
77205e782a | |
|
580c3aa218 | |
|
f8d33f4602 | |
|
2066dbcfe8 | |
|
c3be589c16 | |
|
2a05951680 | |
|
72f79333e5 | |
|
6163c03b11 | |
|
467305fcea | |
|
65a6c35d90 | |
|
c4df0d17bb | |
|
b3cd9d48fe | |
|
581cb2b70a | |
|
6b86aac02e | |
|
8b01d8e74c | |
|
047ea37054 | |
|
4f7f42df0e | |
|
5a23ff9b17 | |
|
8f25f4fb24 | |
|
d16c560664 | |
|
036d3a6bab | |
|
f0e5a0d654 | |
|
ad6ab189a6 | |
|
30774ed1f2 | |
|
306b7445a1 | |
|
ef0a67551a | |
|
a7df96501f | |
|
5a2f87f6f6 | |
|
b8507d71e8 | |
|
cdf705ce66 | |
|
05220c5f19 | |
|
9e331b55d6 | |
|
f40caed86c | |
|
3d87aa441f | |
|
4d9017d789 | |
|
c558c30056 | |
|
823c6a75b3 | |
|
7dfa471387 | |
|
10072c3548 | |
|
323fbc485e | |
|
701b678104 | |
|
7118f1fb4b | |
|
212deb412d | |
|
481e792773 | |
|
6bc7ed8b65 | |
|
a2198ecd14 | |
|
f2c8b9dfd3 | |
|
cfd7e543fc | |
|
7d7a7aac4d | |
|
250e4a564c | |
|
fda64ebc64 | |
|
a5f4ba08d9 | |
|
09cd4ea26c | |
|
a6826de3e2 | |
|
5bcb60aaa6 | |
|
1beb3d4d5b | |
|
0395cdbd71 | |
|
aab947de8f | |
|
5ab12e6262 | |
|
104b07647f | |
|
27734fdf4d | |
|
dcc3d25dc2 | |
|
c36e67d7b6 | |
|
4f944e245b | |
|
7ac3e0e0bf | |
|
0e7d422e5f | |
|
7cb8147e77 | |
|
35a41c39a4 | |
|
abe4aa7893 | |
|
08f876d6e8 | |
|
0f875ba9ad | |
|
d9cafa759f | |
|
ba2c1c94ab | |
|
7ad113ccc2 | |
|
9216f04eb6 | |
|
9fd71c8347 | |
|
045ac0b159 | |
|
03da6ad2d1 | |
|
1df8feb2e4 | |
|
c7cbac58b3 | |
|
e8e4588f64 | |
|
d317bc30be | |
|
832a3754e5 | |
|
4d4533abaa | |
|
7032f5922e | |
|
2966159873 | |
|
65e7ece518 | |
|
5bb8ab4e6f | |
|
8969b57500 | |
|
c6f4573153 | |
|
04bcae3a8c | |
|
c592932f47 | |
|
264080d2fc | |
|
e5a0fb09a3 | |
|
7b172fcf53 | |
|
d223ec3b56 | |
|
206a8da307 | |
|
f969adf63f | |
|
e416418e70 | |
|
efd6e7b6e0 | |
|
f72ec26693 | |
|
6de2cdd1af | |
|
e308036440 | |
|
12d30bb50c | |
|
863b5633f3 | |
|
aa39a7e7be | |
|
1a433cdbdb | |
|
3d2bd97a82 | |
|
70033b78d4 | |
|
8cb8056efa | |
|
7589722e93 | |
|
e06e758d5f | |
|
58bb45c37f | |
|
95c9b1b13b | |
|
c3ee82fdc3 | |
|
9f453d3fea | |
|
f3088e37a0 | |
|
83371c2014 | |
|
bf47419852 | |
|
123ef81f7d | |
|
e626f778ec | |
|
d861b78a8a | |
|
15cf4fa912 | |
|
e3903a1ac8 | |
|
b0d1d94711 | |
|
8c0440a653 | |
|
40e605a3b2 | |
|
873609d790 | |
|
570a17b3bc | |
|
4405c0bd50 | |
|
942a6c4c76 | |
|
56cab16779 | |
|
76c405cfdb | |
|
e650803f09 | |
|
4d93a6486e | |
|
d071c29d4a | |
|
8d99b45d4a | |
|
bd8e3e4440 | |
|
c6b7268932 | |
|
2ce94e4fff | |
|
bf39340294 | |
|
b2b7187244 | |
|
7dfcb06587 | |
|
d4588c711c | |
|
bf9173f383 | |
|
630fe430ff | |
|
3b0edc794c | |
|
89316e18fc | |
|
7a50955006 | |
|
9961e39d40 | |
|
88178eda32 | |
|
cd859b33b4 | |
|
1d34432676 | |
|
e00762ed7d | |
|
6b58c4d545 | |
|
642adae0c0 | |
|
02fda07211 | |
|
ab3fcf9f9b | |
|
78a8856c14 | |
|
4643b42e1d | |
|
86e2a06f1b | |
|
4286883b95 | |
|
2d3b0b33b4 | |
|
c3170f1c81 | |
|
73d88f514b | |
|
9ca4ae9e70 | |
|
11a60e871e | |
|
57ef7eed46 | |
|
9b9d103b29 | |
|
cfb8cb91f2 | |
|
1d571d178d | |
|
2e3d021912 | |
|
13010ba673 | |
|
5c76f7f2d8 | |
|
c2a042e0ed | |
|
e66a1456d3 | |
|
fcb260df1b | |
|
6e5ce4df55 | |
|
ae1727c41e | |
|
38595fecb6 | |
|
4c3fa4ac3c | |
|
6ad1d4617a | |
|
2d979220cc | |
|
cce29da061 | |
|
3265cead1d | |
|
1b9d0762a5 | |
|
6dcf9ac843 | |
|
72f76f2720 | |
|
f9777d2517 | |
|
5934553198 | |
|
a056cc6164 | |
|
15f3e910d1 | |
|
0c07d81a03 | |
|
e0af501e30 | |
|
5f5a5e1297 | |
|
ee05a71513 | |
|
6f0c66c152 | |
|
5f6dfb00d8 | |
|
c06074b22f | |
|
4ead8784d0 | |
|
69854c4e08 | |
|
877a6ef29f | |
|
2bcf433605 | |
|
f14eeeb361 | |
|
5ee2906e78 | |
|
7eab668982 | |
|
ccc1c65859 | |
|
842f8beb00 | |
|
d0ac0acff0 | |
|
187a942a88 | |
|
5a38118956 | |
|
6bd8a4b2b5 | |
|
5a99022556 | |
|
bf13010df8 | |
|
53b6fddced | |
|
4cd9833d7c | |
|
4bff12f476 | |
|
1456b53e4e | |
|
6d9b06d227 | |
|
e8876edcc2 | |
|
eb5b03a8a3 | |
|
b71a055a11 | |
|
c5ea9079af | |
|
f2af519f2e | |
|
bd0546ad5b | |
|
27a7947535 | |
|
53183396d7 | |
|
70f1147394 | |
|
a8f11a2fa2 | |
|
c612e141b5 | |
|
9b7ee0e201 | |
|
3b677449d8 | |
|
d38317c781 | |
|
2dd462cc36 | |
|
4c89455378 | |
|
adbe04b5fc | |
|
097cc9ca64 | |
|
e069ded4c3 | |
|
44eba133d6 | |
|
3529651fa7 | |
|
8324b17f9a | |
|
d16defd9e2 | |
|
3035b6685b | |
|
6769f62746 | |
|
ef38d81fdb | |
|
5052a39915 | |
|
4beddd3e25 | |
|
7026e68a71 | |
|
c0fbbe05ca | |
|
8c22927978 | |
|
c1cc6b61a3 | |
|
3f5b1bdd32 | |
|
845870e669 | |
|
8683664b29 | |
|
d3c23a223c | |
|
081add2fc5 | |
|
8972e53ad0 | |
|
f2c64c123f | |
|
25f95877b5 | |
|
14ed619736 | |
|
7dd9c20cac | |
|
39829affbe | |
|
a93ed48d06 | |
|
f1ceb8c55d | |
|
abfe4d4629 | |
|
68fc942fd2 | |
|
f9431e3b35 | |
|
22cc0e90ae | |
|
de54347518 | |
|
b01d359cc9 | |
|
2abcbf842f | |
|
fcfaa8daeb | |
|
a629a840a8 | |
|
513ceeec0a | |
|
5876b2941c | |
|
50963accec | |
|
d789bac04a | |
|
71460215d3 | |
|
1cc698c68f | |
|
549d39a89f | |
|
54367b3283 | |
|
057f3128b6 | |
|
dfbac70efa | |
|
3b6a556533 | |
|
bf8cb43025 | |
|
a888c4091c | |
|
02d578b637 | |
|
21e8bbc8a2 | |
|
f86ad2ea4c | |
|
a4bf9e78e5 | |
|
a1ea79444b | |
|
066710ba7b | |
|
b8df4abeb5 | |
|
3f0ccd1b71 | |
|
6176a7686e | |
|
66aca29f7d | |
|
f937e62c89 | |
|
bf16dd1251 | |
|
2199a05e08 | |
|
149503a32c | |
|
5c3577ff9f | |
|
14203bbc77 | |
|
b6d7ac34be | |
|
83e507377a | |
|
a0ae6e6a5a | |
|
86b5b528a6 | |
|
f0030712e9 | |
|
89d8c8a2a7 | |
|
e2a4e429bc | |
|
fdc4397906 | |
|
d63cae6f1c | |
|
4bd6b6897f | |
|
a1035b0796 | |
|
7ab3e7e774 | |
|
c6f935eba5 | |
|
2f87a11e96 | |
|
ef7fd8bb67 | |
|
3046019d3b | |
|
1eeb0cc3e1 | |
|
9257cc7f68 | |
|
f214f860b6 | |
|
f907c7a4b0 | |
|
cd277a5815 | |
|
c297770d2d | |
|
219cfc8b7d | |
|
2607ba8062 | |
|
5322affc9f | |
|
dc41365b56 | |
|
dad2e67860 | |
|
7cf245d2f7 | |
|
e0b351b3d9 | |
|
7716219e17 | |
|
f6b90bc253 | |
|
636a4cf2dc | |
|
20181d4363 | |
|
d29d719c42 | |
|
fa169b6933 | |
|
1ca6c946d5 | |
|
c2586d68cf | |
|
a87bde0068 | |
|
df9950aa06 | |
|
323ef1997f | |
|
80be02c72b | |
|
e504faf6da | |
|
03ff54b8ba | |
|
644dc16b16 | |
|
2a2748a94c | |
|
7609dde8d0 | |
|
71bc8ab3ea | |
|
6cf2c023f8 | |
|
73604b8c36 | |
|
e2cab2c64c | |
|
74042f5ffa | |
|
8c317ad3fd | |
|
64f33cd463 | |
|
a3bea24086 | |
|
b05aa464a6 | |
|
e34616574f | |
|
8d2ccc128a | |
|
b5a939268b | |
|
260f1dbebb | |
|
e95d133612 | |
|
3dec3879c8 | |
|
fdc90caeee | |
|
0db7b9f774 | |
|
239b727834 | |
|
907507e22a | |
|
d8089e7d1b | |
|
29263e865b | |
|
6bcc9ce730 | |
|
46b8679315 | |
|
ea4c161067 | |
|
2b56b66b10 | |
|
0ba4362d69 | |
|
8993f54fc3 | |
|
38b99adc10 | |
|
3b25977f82 | |
|
342f8bca25 | |
|
7422403164 | |
|
09a3c93f96 | |
|
a10a1e619b | |
|
75f791d904 | |
|
8d3c0fb6dc | |
|
45f09a1504 | |
|
ce639151e0 | |
|
52c62bd13b | |
|
8f865184a6 | |
|
ee957e144b | |
|
6291744fa4 | |
|
60b326f814 | |
|
aa6ad06304 | |
|
66713384c3 | |
|
5c21ec520e | |
|
212213e81e | |
|
bcd6c45731 | |
|
876fc1dac4 | |
|
3bfb30acd7 | |
|
3f4cc89f64 | |
|
a12090d787 | |
|
f1b0ef127d | |
|
26a11366a7 | |
|
9e39630a05 | |
|
6d2a901118 | |
|
389ada7188 | |
|
613477b489 | |
|
a4c8c72411 | |
|
5896d383ca | |
|
ea850377cd | |
|
2d0d4ce4af | |
|
a0d9b0cf0d | |
|
b3d0327781 | |
|
70aef9f502 | |
|
80dd489f21 | |
|
04e2a24a9e | |
|
84c375f430 | |
|
71672ece9c | |
|
db857b5d9c | |
|
242f176825 | |
|
6ea4877cff | |
|
7bc503344a | |
|
e2cc22d076 | |
|
e9831d75e2 | |
|
9450481b7e | |
|
a6cc6cd878 | |
|
e907d54fe6 |
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -63,7 +63,7 @@ jobs:
|
||||||
name: Update Go
|
name: Update Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.5"
|
go-version: "1.24.7"
|
||||||
-
|
-
|
||||||
name: Initialize CodeQL
|
name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v3
|
||||||
|
|
|
@ -44,7 +44,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
-
|
-
|
||||||
name: Update daemon.json
|
name: Update daemon.json
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -59,14 +59,14 @@ jobs:
|
||||||
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@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.5"
|
go-version: "1.24.7"
|
||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -11,18 +11,23 @@ permissions:
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, edited, labeled, unlabeled]
|
types: [opened, edited, labeled, unlabeled, synchronize]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-area-label:
|
check-labels:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 120 # guardrails timeout for the whole job
|
timeout-minutes: 120 # guardrails timeout for the whole job
|
||||||
steps:
|
steps:
|
||||||
- name: Missing `area/` label
|
- name: Missing `area/` label
|
||||||
if: contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'area/')
|
if: always() && contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'area/')
|
||||||
run: |
|
run: |
|
||||||
echo "::error::Every PR with an 'impact/*' label should also have an 'area/*' label"
|
echo "::error::Every PR with an 'impact/*' label should also have an 'area/*' label"
|
||||||
exit 1
|
exit 1
|
||||||
|
- name: Missing `kind/` label
|
||||||
|
if: always() && contains(join(github.event.pull_request.labels.*.name, ','), 'impact/') && !contains(join(github.event.pull_request.labels.*.name, ','), 'kind/')
|
||||||
|
run: |
|
||||||
|
echo "::error::Every PR with an 'impact/*' label should also have a 'kind/*' label"
|
||||||
|
exit 1
|
||||||
- name: OK
|
- name: OK
|
||||||
run: exit 0
|
run: exit 0
|
||||||
|
|
||||||
|
|
|
@ -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}"'
|
||||||
|
|
|
@ -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.7"
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -5,10 +5,10 @@ ARG BASE_VARIANT=alpine
|
||||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||||
# that's also available as alpine image variant for the Golang version used.
|
# that's also available as alpine image variant for the Golang version used.
|
||||||
ARG ALPINE_VERSION=3.21
|
ARG ALPINE_VERSION=3.22
|
||||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||||
|
|
||||||
ARG GO_VERSION=1.24.5
|
ARG GO_VERSION=1.24.7
|
||||||
ARG XX_VERSION=1.6.1
|
ARG XX_VERSION=1.6.1
|
||||||
ARG GOVERSIONINFO_VERSION=v1.4.1
|
ARG GOVERSIONINFO_VERSION=v1.4.1
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
package manager
|
|
||||||
|
|
||||||
import "github.com/docker/cli/cli-plugins/metadata"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// CommandAnnotationPlugin is added to every stub command added by
|
|
||||||
// AddPluginCommandStubs with the value "true" and so can be
|
|
||||||
// used to distinguish plugin stubs from regular commands.
|
|
||||||
CommandAnnotationPlugin = metadata.CommandAnnotationPlugin
|
|
||||||
|
|
||||||
// CommandAnnotationPluginVendor is added to every stub command
|
|
||||||
// added by AddPluginCommandStubs and contains the vendor of
|
|
||||||
// that plugin.
|
|
||||||
CommandAnnotationPluginVendor = metadata.CommandAnnotationPluginVendor
|
|
||||||
|
|
||||||
// CommandAnnotationPluginVersion is added to every stub command
|
|
||||||
// added by AddPluginCommandStubs and contains the version of
|
|
||||||
// that plugin.
|
|
||||||
CommandAnnotationPluginVersion = metadata.CommandAnnotationPluginVersion
|
|
||||||
|
|
||||||
// CommandAnnotationPluginInvalid is added to any stub command
|
|
||||||
// added by AddPluginCommandStubs for an invalid command (that
|
|
||||||
// is, one which failed it's candidate test) and contains the
|
|
||||||
// reason for the failure.
|
|
||||||
CommandAnnotationPluginInvalid = metadata.CommandAnnotationPluginInvalid
|
|
||||||
|
|
||||||
// CommandAnnotationPluginCommandPath is added to overwrite the
|
|
||||||
// command path for a plugin invocation.
|
|
||||||
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,83 @@ 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
|
||||||
}{
|
}{
|
||||||
/* 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 "" is not valid`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid schemaversion",
|
||||||
|
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`},
|
||||||
|
invalid: `plugin SchemaVersion "xyzzy" is not valid`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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"}`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 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}`},
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
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)
|
||||||
|
|
|
@ -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>)"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ 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"
|
||||||
|
@ -17,20 +18,6 @@ import (
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// ReexecEnvvar is the name of an ennvar which is set to the command
|
|
||||||
// used to originally invoke the docker CLI when executing a
|
|
||||||
// plugin. Assuming $PATH and $CWD remain unchanged this should allow
|
|
||||||
// the plugin to re-execute the original CLI.
|
|
||||||
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.
|
|
||||||
ResourceAttributesEnvvar = "OTEL_RESOURCE_ATTRIBUTES"
|
|
||||||
)
|
|
||||||
|
|
||||||
// errPluginNotFound is the error returned when a plugin could not be found.
|
// errPluginNotFound is the error returned when a plugin could not be found.
|
||||||
type errPluginNotFound string
|
type errPluginNotFound string
|
||||||
|
|
||||||
|
@ -40,17 +27,6 @@ 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.
|
|
||||||
func IsNotFound(err error) bool {
|
|
||||||
if e, ok := err.(*pluginError); ok {
|
|
||||||
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
|
||||||
// in order of preference.
|
// in order of preference.
|
||||||
//
|
//
|
||||||
|
@ -127,7 +103,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 +140,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,15 +161,15 @@ 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
|
||||||
// they lack e.g. global options which we must propagate here.
|
// they lack e.g. global options which we must propagate here.
|
||||||
args := os.Args[1:]
|
args := os.Args[1:]
|
||||||
if !pluginNameRe.MatchString(name) {
|
if !isValidPluginName(name) {
|
||||||
// We treat this as "not found" so that callers will
|
// We treat this as "not found" so that callers will
|
||||||
// fallback to their "invalid" command path.
|
// fallback to their "invalid" command path.
|
||||||
return nil, errPluginNotFound(name)
|
return nil, errPluginNotFound(name)
|
||||||
|
|
|
@ -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"
|
||||||
|
@ -131,7 +132,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 +167,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) {
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/cli/cli-plugins/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NamePrefix is the prefix required on all plugin binary names
|
|
||||||
NamePrefix = metadata.NamePrefix
|
|
||||||
|
|
||||||
// MetadataSubcommandName is the name of the plugin subcommand
|
|
||||||
// which must be supported by every plugin and returns the
|
|
||||||
// plugin metadata.
|
|
||||||
MetadataSubcommandName = metadata.MetadataSubcommandName
|
|
||||||
|
|
||||||
// HookSubcommandName is the name of the plugin subcommand
|
|
||||||
// which must be implemented by plugins declaring support
|
|
||||||
// for hooks in their metadata.
|
|
||||||
HookSubcommandName = metadata.HookSubcommandName
|
|
||||||
)
|
|
||||||
|
|
||||||
// Metadata provided by the plugin.
|
|
||||||
type Metadata = metadata.Metadata
|
|
|
@ -2,6 +2,7 @@ package manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -11,12 +12,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli-plugins/metadata"
|
"github.com/docker/cli/cli-plugins/metadata"
|
||||||
"github.com/docker/cli/internal/lazyregexp"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var pluginNameRe = lazyregexp.New("^[a-z][a-z0-9]*$")
|
|
||||||
|
|
||||||
// Plugin represents a potential plugin with all it's metadata.
|
// Plugin represents a potential plugin with all it's metadata.
|
||||||
type Plugin struct {
|
type Plugin struct {
|
||||||
metadata.Metadata
|
metadata.Metadata
|
||||||
|
@ -31,12 +29,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")
|
||||||
|
@ -62,8 +82,8 @@ 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 !isValidPluginName(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, pluginNameFormat)
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,11 +95,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,11 +116,11 @@ func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
if p.Metadata.SchemaVersion != "0.1.0" {
|
if p.Metadata.SchemaVersion != "0.1.0" {
|
||||||
p.Err = NewPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
|
p.Err = newPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
|
||||||
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
|
||||||
|
@ -124,3 +144,26 @@ func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte,
|
||||||
|
|
||||||
return hookCmdOutput, nil
|
return hookCmdOutput, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pluginNameFormat is used as part of errors for invalid plugin-names.
|
||||||
|
// We should consider making this less technical ("must start with "a-z",
|
||||||
|
// and only consist of lowercase alphanumeric characters").
|
||||||
|
const pluginNameFormat = `^[a-z][a-z0-9]*$`
|
||||||
|
|
||||||
|
func isValidPluginName(s string) bool {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// first character must be a-z
|
||||||
|
if c := s[0]; c < 'a' || c > 'z' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// followed by a-z or 0-9
|
||||||
|
for i := 1; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if (c < 'a' || c > 'z') && (c < '0' || c > '9') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ import (
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/connhelper"
|
"github.com/docker/cli/cli/connhelper"
|
||||||
"github.com/docker/cli/cli/debug"
|
"github.com/docker/cli/cli/debug"
|
||||||
"github.com/docker/docker/client"
|
"github.com/moby/moby/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
)
|
)
|
||||||
|
@ -175,11 +175,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)
|
||||||
|
}
|
||||||
|
}
|
32
cli/cobra.go
32
cli/cobra.go
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/fvbommel/sortorder"
|
"github.com/fvbommel/sortorder"
|
||||||
"github.com/moby/term"
|
"github.com/moby/term"
|
||||||
"github.com/morikuni/aec"
|
"github.com/morikuni/aec"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
@ -167,35 +166,6 @@ func (tcmd *TopLevelCommand) Initialize(ops ...command.CLIOption) error {
|
||||||
return tcmd.dockerCli.Initialize(tcmd.opts, ops...)
|
return tcmd.dockerCli.Initialize(tcmd.opts, ops...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VisitAll will traverse all commands from the root.
|
|
||||||
// This is different from the VisitAll of cobra.Command where only parents
|
|
||||||
// are checked.
|
|
||||||
func VisitAll(root *cobra.Command, fn func(*cobra.Command)) {
|
|
||||||
for _, cmd := range root.Commands() {
|
|
||||||
VisitAll(cmd, fn)
|
|
||||||
}
|
|
||||||
fn(root)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all
|
|
||||||
// commands within the tree rooted at cmd.
|
|
||||||
func DisableFlagsInUseLine(cmd *cobra.Command) {
|
|
||||||
VisitAll(cmd, func(ccmd *cobra.Command) {
|
|
||||||
// do not add a `[flags]` to the end of the usage line.
|
|
||||||
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",
|
||||||
|
@ -204,7 +174,7 @@ var helpCommand = &cobra.Command{
|
||||||
RunE: func(c *cobra.Command, args []string) error {
|
RunE: func(c *cobra.Command, args []string) error {
|
||||||
cmd, args, e := c.Root().Find(args)
|
cmd, args, e := c.Root().Find(args)
|
||||||
if cmd == nil || e != nil || len(args) > 0 {
|
if cmd == nil || e != nil || len(args) > 0 {
|
||||||
return errors.Errorf("unknown help topic: %v", strings.Join(args, " "))
|
return fmt.Errorf("unknown help topic: %v", strings.Join(args, " "))
|
||||||
}
|
}
|
||||||
helpFunc := cmd.HelpFunc()
|
helpFunc := cmd.HelpFunc()
|
||||||
helpFunc(cmd, args)
|
helpFunc(cmd, args)
|
||||||
|
|
|
@ -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"}), "")
|
||||||
|
|
|
@ -3,8 +3,8 @@ package builder
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/build"
|
"github.com/moby/moby/api/types/build"
|
||||||
"github.com/docker/docker/client"
|
"github.com/moby/moby/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeClient struct {
|
type fakeClient struct {
|
||||||
|
|
|
@ -6,29 +6,41 @@ 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/image"
|
"github.com/docker/cli/cli/command/image"
|
||||||
|
"github.com/docker/cli/internal/commands"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewBuilderCommand returns a cobra command for `builder` subcommands
|
func init() {
|
||||||
func NewBuilderCommand(dockerCli command.Cli) *cobra.Command {
|
commands.Register(newBuilderCommand)
|
||||||
|
commands.Register(func(c command.Cli) *cobra.Command {
|
||||||
|
return newBakeStubCommand(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBuilderCommand returns a cobra command for `builder` subcommands
|
||||||
|
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"},
|
||||||
|
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBakeStubCommand returns a cobra command "stub" for the "bake" subcommand.
|
// newBakeStubCommand returns a cobra command "stub" for the "bake" subcommand.
|
||||||
// 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).
|
||||||
func NewBakeStubCommand(dockerCLI command.Streams) *cobra.Command {
|
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",
|
||||||
|
@ -40,5 +52,6 @@ func NewBakeStubCommand(dockerCLI command.Streams) *cobra.Command {
|
||||||
"aliases": "docker buildx bake",
|
"aliases": "docker buildx bake",
|
||||||
"version": "1.31",
|
"version": "1.31",
|
||||||
},
|
},
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,22 @@ 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/system/pruner"
|
||||||
"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/go-units"
|
"github.com/docker/go-units"
|
||||||
|
"github.com/moby/moby/api/types/build"
|
||||||
|
"github.com/moby/moby/api/types/versions"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Register the prune command to run as part of "docker system prune"
|
||||||
|
if err := pruner.Register(pruner.TypeBuildCache, pruneFn); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type pruneOptions struct {
|
type pruneOptions struct {
|
||||||
force bool
|
force bool
|
||||||
all bool
|
all bool
|
||||||
|
@ -23,8 +31,8 @@ type pruneOptions struct {
|
||||||
keepStorage opts.MemBytes
|
keepStorage opts.MemBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPruneCommand returns a new cobra prune command for images
|
// newPruneCommand returns a new cobra prune command for images
|
||||||
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
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 +40,19 @@ 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,
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
@ -104,7 +113,30 @@ type cancelledErr struct{ error }
|
||||||
|
|
||||||
func (cancelledErr) Cancelled() {}
|
func (cancelledErr) Cancelled() {}
|
||||||
|
|
||||||
// CachePrune executes a prune command for build cache
|
type errNotImplemented struct{ error }
|
||||||
func CachePrune(ctx context.Context, dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
|
|
||||||
return runPrune(ctx, dockerCli, pruneOptions{force: true, all: all, filter: filter})
|
func (errNotImplemented) NotImplemented() {}
|
||||||
|
|
||||||
|
// pruneFn prunes the build cache for use in "docker system prune" and
|
||||||
|
// returns the amount of space reclaimed and a detailed output string.
|
||||||
|
func pruneFn(ctx context.Context, dockerCLI command.Cli, options pruner.PruneOptions) (uint64, string, error) {
|
||||||
|
if ver := dockerCLI.Client().ClientVersion(); ver != "" && versions.LessThan(ver, "1.31") {
|
||||||
|
// Not supported on older daemons.
|
||||||
|
return 0, "", errNotImplemented{errors.New("builder prune requires API version 1.31 or greater")}
|
||||||
|
}
|
||||||
|
if !options.Confirmed {
|
||||||
|
// Dry-run: perform validation and produce confirmation before pruning.
|
||||||
|
var confirmMsg string
|
||||||
|
if options.All {
|
||||||
|
confirmMsg = "all build cache"
|
||||||
|
} else {
|
||||||
|
confirmMsg = "unused build cache"
|
||||||
|
}
|
||||||
|
return 0, confirmMsg, cancelledErr{errors.New("builder prune has been cancelled")}
|
||||||
|
}
|
||||||
|
return runPrune(ctx, dockerCLI, pruneOptions{
|
||||||
|
force: true,
|
||||||
|
all: options.All,
|
||||||
|
filter: options.Filter,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/build"
|
"github.com/moby/moby/api/types/build"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderPromptTermination(t *testing.T) {
|
func TestBuilderPromptTermination(t *testing.T) {
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -3,8 +3,8 @@ package checkpoint
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/checkpoint"
|
"github.com/moby/moby/api/types/checkpoint"
|
||||||
"github.com/docker/docker/client"
|
"github.com/moby/moby/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeClient struct {
|
type fakeClient struct {
|
||||||
|
|
|
@ -3,26 +3,32 @@ package checkpoint
|
||||||
import (
|
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/internal/commands"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewCheckpointCommand returns the `checkpoint` subcommand (only in experimental)
|
func init() {
|
||||||
func NewCheckpointCommand(dockerCli command.Cli) *cobra.Command {
|
commands.Register(newCheckpointCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCheckpointCommand returns the `checkpoint` subcommand (only in experimental)
|
||||||
|
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",
|
||||||
"version": "1.25",
|
"version": "1.25",
|
||||||
},
|
},
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
newCreateCommand(dockerCli),
|
newCreateCommand(dockerCLI),
|
||||||
newListCommand(dockerCli),
|
newListCommand(dockerCLI),
|
||||||
newRemoveCommand(dockerCli),
|
newRemoveCommand(dockerCLI),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,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/moby/moby/api/types/checkpoint"
|
||||||
"github.com/docker/docker/api/types/checkpoint"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,7 +17,7 @@ type createOptions struct {
|
||||||
leaveRunning bool
|
leaveRunning bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts createOptions
|
var opts createOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -28,9 +27,10 @@ func newCreateCommand(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.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,
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/checkpoint"
|
"github.com/moby/moby/api/types/checkpoint"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@ package checkpoint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/docker/api/types/checkpoint"
|
"github.com/moby/moby/api/types/checkpoint"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -10,25 +10,31 @@ const (
|
||||||
checkpointNameHeader = "CHECKPOINT NAME"
|
checkpointNameHeader = "CHECKPOINT NAME"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewFormat returns a format for use with a checkpoint Context
|
// newFormat returns a format for use with a checkpointContext.
|
||||||
func NewFormat(source string) formatter.Format {
|
func newFormat(source string) formatter.Format {
|
||||||
if source == formatter.TableFormatKey {
|
if source == formatter.TableFormatKey {
|
||||||
return defaultCheckpointFormat
|
return defaultCheckpointFormat
|
||||||
}
|
}
|
||||||
return formatter.Format(source)
|
return formatter.Format(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatWrite writes formatted checkpoints using the Context
|
// formatWrite writes formatted checkpoints using the Context
|
||||||
func FormatWrite(ctx formatter.Context, checkpoints []checkpoint.Summary) error {
|
func formatWrite(fmtCtx formatter.Context, checkpoints []checkpoint.Summary) error {
|
||||||
render := func(format func(subContext formatter.SubContext) error) error {
|
cpContext := &checkpointContext{
|
||||||
|
HeaderContext: formatter.HeaderContext{
|
||||||
|
Header: formatter.SubHeaderContext{
|
||||||
|
"Name": checkpointNameHeader,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return fmtCtx.Write(cpContext, 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 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
})
|
||||||
return ctx.Write(newCheckpointContext(), render)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type checkpointContext struct {
|
type checkpointContext struct {
|
||||||
|
@ -36,14 +42,6 @@ type checkpointContext struct {
|
||||||
c checkpoint.Summary
|
c checkpoint.Summary
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCheckpointContext() *checkpointContext {
|
|
||||||
cpCtx := checkpointContext{}
|
|
||||||
cpCtx.Header = formatter.SubHeaderContext{
|
|
||||||
"Name": checkpointNameHeader,
|
|
||||||
}
|
|
||||||
return &cpCtx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *checkpointContext) MarshalJSON() ([]byte, error) {
|
func (c *checkpointContext) MarshalJSON() ([]byte, error) {
|
||||||
return formatter.MarshalJSON(c)
|
return formatter.MarshalJSON(c)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/docker/api/types/checkpoint"
|
"github.com/moby/moby/api/types/checkpoint"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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"},
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"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/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/docker/api/types/checkpoint"
|
"github.com/moby/moby/api/types/checkpoint"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ type listOptions struct {
|
||||||
checkpointDir string
|
checkpointDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
func newListCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts listOptions
|
var opts listOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -24,9 +24,10 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
Short: "List checkpoints for a container",
|
Short: "List checkpoints for a container",
|
||||||
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 runList(cmd.Context(), dockerCli, args[0], opts)
|
return runList(cmd.Context(), dockerCLI, args[0], opts)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
@ -45,7 +46,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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/checkpoint"
|
"github.com/moby/moby/api/types/checkpoint"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
|
|
|
@ -5,7 +5,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/docker/api/types/checkpoint"
|
"github.com/moby/moby/api/types/checkpoint"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ type removeOptions struct {
|
||||||
checkpointDir string
|
checkpointDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
func newRemoveCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts removeOptions
|
var opts removeOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -22,8 +22,9 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
Short: "Remove a checkpoint",
|
Short: "Remove a checkpoint",
|
||||||
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 runRemove(cmd.Context(), dockerCli, args[0], args[1], opts)
|
return runRemove(cmd.Context(), dockerCLI, args[0], args[1], opts)
|
||||||
},
|
},
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/checkpoint"
|
"github.com/moby/moby/api/types/checkpoint"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,7 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -23,11 +24,9 @@ import (
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/cli/cli/version"
|
"github.com/docker/cli/cli/version"
|
||||||
dopts "github.com/docker/cli/opts"
|
dopts "github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api"
|
"github.com/moby/moby/api/types/build"
|
||||||
"github.com/docker/docker/api/types/build"
|
"github.com/moby/moby/api/types/swarm"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/moby/moby/client"
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -88,9 +87,9 @@ type DockerCli struct {
|
||||||
enableGlobalMeter, enableGlobalTracer bool
|
enableGlobalMeter, enableGlobalTracer bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultVersion returns [api.DefaultVersion].
|
// DefaultVersion returns [client.DefaultAPIVersion].
|
||||||
func (*DockerCli) DefaultVersion() string {
|
func (*DockerCli) DefaultVersion() string {
|
||||||
return api.DefaultVersion
|
return client.DefaultAPIVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentVersion returns the API version currently negotiated, or the default
|
// CurrentVersion returns the API version currently negotiated, or the default
|
||||||
|
@ -98,7 +97,7 @@ func (*DockerCli) DefaultVersion() string {
|
||||||
func (cli *DockerCli) CurrentVersion() string {
|
func (cli *DockerCli) CurrentVersion() string {
|
||||||
_ = cli.initialize()
|
_ = cli.initialize()
|
||||||
if cli.client == nil {
|
if cli.client == nil {
|
||||||
return api.DefaultVersion
|
return client.DefaultAPIVersion
|
||||||
}
|
}
|
||||||
return cli.client.ClientVersion()
|
return cli.client.ClientVersion()
|
||||||
}
|
}
|
||||||
|
@ -169,7 +168,7 @@ func (cli *DockerCli) BuildKitEnabled() (bool, error) {
|
||||||
if v := os.Getenv("DOCKER_BUILDKIT"); v != "" {
|
if v := os.Getenv("DOCKER_BUILDKIT"); v != "" {
|
||||||
enabled, err := strconv.ParseBool(v)
|
enabled, err := strconv.ParseBool(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, "DOCKER_BUILDKIT environment variable expects boolean value")
|
return false, fmt.Errorf("DOCKER_BUILDKIT environment variable expects boolean value: %w", err)
|
||||||
}
|
}
|
||||||
return enabled, nil
|
return enabled, nil
|
||||||
}
|
}
|
||||||
|
@ -282,6 +281,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,7 +310,7 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.
|
||||||
}
|
}
|
||||||
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, fmt.Errorf("unable to resolve docker endpoint: %w", err)
|
||||||
}
|
}
|
||||||
return newAPIClientFromEndpoint(endpoint, configFile)
|
return newAPIClientFromEndpoint(endpoint, configFile)
|
||||||
}
|
}
|
||||||
|
@ -475,11 +485,62 @@ 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()
|
||||||
if cli.initErr != nil {
|
if cli.initErr != nil {
|
||||||
cli.initErr = errors.Wrap(cli.initErr, "unable to resolve docker endpoint")
|
cli.initErr = fmt.Errorf("unable to resolve docker endpoint: %w", cli.initErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if cli.client == nil {
|
if cli.client == nil {
|
||||||
|
@ -547,7 +608,7 @@ func getServerHost(hosts []string, defaultToTLS bool) (string, error) {
|
||||||
case 1:
|
case 1:
|
||||||
return dopts.ParseHost(defaultToTLS, hosts[0])
|
return dopts.ParseHost(defaultToTLS, hosts[0])
|
||||||
default:
|
default:
|
||||||
return "", errors.New("Specify only one -H")
|
return "", errors.New("specify only one -H")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package command
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -10,9 +11,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/docker/client"
|
"github.com/moby/moby/client"
|
||||||
"github.com/moby/term"
|
"github.com/moby/term"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CLIOption is a functional argument to apply options to a [DockerCli]. These
|
// CLIOption is a functional argument to apply options to a [DockerCli]. These
|
||||||
|
@ -189,7 +189,7 @@ func withCustomHeadersFromEnv() client.Opt {
|
||||||
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 invalidParameter(errors.Errorf(
|
return invalidParameter(fmt.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,
|
||||||
))
|
))
|
||||||
|
@ -206,7 +206,7 @@ func withCustomHeadersFromEnv() client.Opt {
|
||||||
k = strings.TrimSpace(k)
|
k = strings.TrimSpace(k)
|
||||||
|
|
||||||
if k == "" {
|
if k == "" {
|
||||||
return invalidParameter(errors.Errorf(
|
return invalidParameter(fmt.Errorf(
|
||||||
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
|
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
|
||||||
envOverrideHTTPHeaders, kv,
|
envOverrideHTTPHeaders, kv,
|
||||||
))
|
))
|
||||||
|
@ -217,7 +217,7 @@ func withCustomHeadersFromEnv() client.Opt {
|
||||||
// from an environment variable with the same name). In the meantime,
|
// from an environment variable with the same name). In the meantime,
|
||||||
// produce an error to prevent users from depending on this.
|
// produce an error to prevent users from depending on this.
|
||||||
if !hasValue {
|
if !hasValue {
|
||||||
return invalidParameter(errors.Errorf(
|
return invalidParameter(fmt.Errorf(
|
||||||
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
|
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
|
||||||
envOverrideHTTPHeaders, kv,
|
envOverrideHTTPHeaders, kv,
|
||||||
))
|
))
|
||||||
|
|
|
@ -18,10 +18,10 @@ 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/moby/moby/api/types"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/moby/moby/client"
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func TestNewAPIClientFromFlags(t *testing.T) {
|
||||||
apiClient, err := NewAPIClientFromFlags(opts, &configfile.ConfigFile{})
|
apiClient, err := NewAPIClientFromFlags(opts, &configfile.ConfigFile{})
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Equal(t, apiClient.DaemonHost(), host)
|
assert.Equal(t, apiClient.DaemonHost(), host)
|
||||||
assert.Equal(t, apiClient.ClientVersion(), api.DefaultVersion)
|
assert.Equal(t, apiClient.ClientVersion(), client.DefaultAPIVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewAPIClientFromFlagsForDefaultSchema(t *testing.T) {
|
func TestNewAPIClientFromFlagsForDefaultSchema(t *testing.T) {
|
||||||
|
@ -47,7 +47,7 @@ func TestNewAPIClientFromFlagsForDefaultSchema(t *testing.T) {
|
||||||
apiClient, err := NewAPIClientFromFlags(opts, &configfile.ConfigFile{})
|
apiClient, err := NewAPIClientFromFlags(opts, &configfile.ConfigFile{})
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Equal(t, apiClient.DaemonHost(), slug+host)
|
assert.Equal(t, apiClient.DaemonHost(), slug+host)
|
||||||
assert.Equal(t, apiClient.ClientVersion(), api.DefaultVersion)
|
assert.Equal(t, apiClient.ClientVersion(), client.DefaultAPIVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewAPIClientFromFlagsWithCustomHeaders(t *testing.T) {
|
func TestNewAPIClientFromFlagsWithCustomHeaders(t *testing.T) {
|
||||||
|
@ -71,7 +71,7 @@ func TestNewAPIClientFromFlagsWithCustomHeaders(t *testing.T) {
|
||||||
apiClient, err := NewAPIClientFromFlags(opts, configFile)
|
apiClient, err := NewAPIClientFromFlags(opts, configFile)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Equal(t, apiClient.DaemonHost(), host)
|
assert.Equal(t, apiClient.DaemonHost(), host)
|
||||||
assert.Equal(t, apiClient.ClientVersion(), api.DefaultVersion)
|
assert.Equal(t, apiClient.ClientVersion(), client.DefaultAPIVersion)
|
||||||
|
|
||||||
// verify User-Agent is not appended to the configfile. see https://github.com/docker/cli/pull/2756
|
// verify User-Agent is not appended to the configfile. see https://github.com/docker/cli/pull/2756
|
||||||
assert.DeepEqual(t, configFile.HTTPHeaders, map[string]string{"My-Header": "Custom-Value"})
|
assert.DeepEqual(t, configFile.HTTPHeaders, map[string]string{"My-Header": "Custom-Value"})
|
||||||
|
@ -106,7 +106,7 @@ func TestNewAPIClientFromFlagsWithCustomHeadersFromEnv(t *testing.T) {
|
||||||
apiClient, err := NewAPIClientFromFlags(opts, configFile)
|
apiClient, err := NewAPIClientFromFlags(opts, configFile)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Equal(t, apiClient.DaemonHost(), host)
|
assert.Equal(t, apiClient.DaemonHost(), host)
|
||||||
assert.Equal(t, apiClient.ClientVersion(), api.DefaultVersion)
|
assert.Equal(t, apiClient.ClientVersion(), client.DefaultAPIVersion)
|
||||||
|
|
||||||
expectedHeaders := http.Header{
|
expectedHeaders := http.Header{
|
||||||
"One": []string{"one-value"},
|
"One": []string{"one-value"},
|
||||||
|
@ -188,16 +188,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 +353,23 @@ 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"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -1,110 +1,31 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/builder"
|
_ "github.com/docker/cli/cli/command/builder"
|
||||||
"github.com/docker/cli/cli/command/checkpoint"
|
_ "github.com/docker/cli/cli/command/checkpoint"
|
||||||
"github.com/docker/cli/cli/command/config"
|
_ "github.com/docker/cli/cli/command/config"
|
||||||
"github.com/docker/cli/cli/command/container"
|
_ "github.com/docker/cli/cli/command/container"
|
||||||
"github.com/docker/cli/cli/command/context"
|
_ "github.com/docker/cli/cli/command/context"
|
||||||
"github.com/docker/cli/cli/command/image"
|
_ "github.com/docker/cli/cli/command/image"
|
||||||
"github.com/docker/cli/cli/command/manifest"
|
_ "github.com/docker/cli/cli/command/manifest"
|
||||||
"github.com/docker/cli/cli/command/network"
|
_ "github.com/docker/cli/cli/command/network"
|
||||||
"github.com/docker/cli/cli/command/node"
|
_ "github.com/docker/cli/cli/command/node"
|
||||||
"github.com/docker/cli/cli/command/plugin"
|
_ "github.com/docker/cli/cli/command/plugin"
|
||||||
"github.com/docker/cli/cli/command/registry"
|
_ "github.com/docker/cli/cli/command/registry"
|
||||||
"github.com/docker/cli/cli/command/secret"
|
_ "github.com/docker/cli/cli/command/secret"
|
||||||
"github.com/docker/cli/cli/command/service"
|
_ "github.com/docker/cli/cli/command/service"
|
||||||
"github.com/docker/cli/cli/command/stack"
|
_ "github.com/docker/cli/cli/command/stack"
|
||||||
"github.com/docker/cli/cli/command/swarm"
|
_ "github.com/docker/cli/cli/command/swarm"
|
||||||
"github.com/docker/cli/cli/command/system"
|
_ "github.com/docker/cli/cli/command/system"
|
||||||
"github.com/docker/cli/cli/command/trust"
|
_ "github.com/docker/cli/cli/command/trust"
|
||||||
"github.com/docker/cli/cli/command/volume"
|
_ "github.com/docker/cli/cli/command/volume"
|
||||||
|
"github.com/docker/cli/internal/commands"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddCommands adds all the commands from cli/command to the root command
|
func AddCommands(cmd *cobra.Command, dockerCLI command.Cli) {
|
||||||
func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
for _, c := range commands.Commands() {
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(c(dockerCLI))
|
||||||
// commonly used shorthands
|
|
||||||
container.NewRunCommand(dockerCli),
|
|
||||||
container.NewExecCommand(dockerCli),
|
|
||||||
container.NewPsCommand(dockerCli),
|
|
||||||
image.NewBuildCommand(dockerCli),
|
|
||||||
image.NewPullCommand(dockerCli),
|
|
||||||
image.NewPushCommand(dockerCli),
|
|
||||||
image.NewImagesCommand(dockerCli),
|
|
||||||
registry.NewLoginCommand(dockerCli),
|
|
||||||
registry.NewLogoutCommand(dockerCli),
|
|
||||||
registry.NewSearchCommand(dockerCli),
|
|
||||||
system.NewVersionCommand(dockerCli),
|
|
||||||
system.NewInfoCommand(dockerCli),
|
|
||||||
|
|
||||||
// management commands
|
|
||||||
builder.NewBakeStubCommand(dockerCli),
|
|
||||||
builder.NewBuilderCommand(dockerCli),
|
|
||||||
checkpoint.NewCheckpointCommand(dockerCli),
|
|
||||||
container.NewContainerCommand(dockerCli),
|
|
||||||
context.NewContextCommand(dockerCli),
|
|
||||||
image.NewImageCommand(dockerCli),
|
|
||||||
manifest.NewManifestCommand(dockerCli),
|
|
||||||
network.NewNetworkCommand(dockerCli),
|
|
||||||
plugin.NewPluginCommand(dockerCli),
|
|
||||||
system.NewSystemCommand(dockerCli),
|
|
||||||
trust.NewTrustCommand(dockerCli),
|
|
||||||
volume.NewVolumeCommand(dockerCli),
|
|
||||||
|
|
||||||
// orchestration (swarm) commands
|
|
||||||
config.NewConfigCommand(dockerCli),
|
|
||||||
node.NewNodeCommand(dockerCli),
|
|
||||||
secret.NewSecretCommand(dockerCli),
|
|
||||||
service.NewServiceCommand(dockerCli),
|
|
||||||
stack.NewStackCommand(dockerCli),
|
|
||||||
swarm.NewSwarmCommand(dockerCli),
|
|
||||||
|
|
||||||
// legacy commands may be hidden
|
|
||||||
hide(container.NewAttachCommand(dockerCli)),
|
|
||||||
hide(container.NewCommitCommand(dockerCli)),
|
|
||||||
hide(container.NewCopyCommand(dockerCli)),
|
|
||||||
hide(container.NewCreateCommand(dockerCli)),
|
|
||||||
hide(container.NewDiffCommand(dockerCli)),
|
|
||||||
hide(container.NewExportCommand(dockerCli)),
|
|
||||||
hide(container.NewKillCommand(dockerCli)),
|
|
||||||
hide(container.NewLogsCommand(dockerCli)),
|
|
||||||
hide(container.NewPauseCommand(dockerCli)),
|
|
||||||
hide(container.NewPortCommand(dockerCli)),
|
|
||||||
hide(container.NewRenameCommand(dockerCli)),
|
|
||||||
hide(container.NewRestartCommand(dockerCli)),
|
|
||||||
hide(container.NewRmCommand(dockerCli)),
|
|
||||||
hide(container.NewStartCommand(dockerCli)),
|
|
||||||
hide(container.NewStatsCommand(dockerCli)),
|
|
||||||
hide(container.NewStopCommand(dockerCli)),
|
|
||||||
hide(container.NewTopCommand(dockerCli)),
|
|
||||||
hide(container.NewUnpauseCommand(dockerCli)),
|
|
||||||
hide(container.NewUpdateCommand(dockerCli)),
|
|
||||||
hide(container.NewWaitCommand(dockerCli)),
|
|
||||||
hide(image.NewHistoryCommand(dockerCli)),
|
|
||||||
hide(image.NewImportCommand(dockerCli)),
|
|
||||||
hide(image.NewLoadCommand(dockerCli)),
|
|
||||||
hide(image.NewRemoveCommand(dockerCli)),
|
|
||||||
hide(image.NewSaveCommand(dockerCli)),
|
|
||||||
hide(image.NewTagCommand(dockerCli)),
|
|
||||||
hide(system.NewEventsCommand(dockerCli)),
|
|
||||||
hide(system.NewInspectCommand(dockerCli)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hide(cmd *cobra.Command) *cobra.Command {
|
|
||||||
// If the environment variable with name "DOCKER_HIDE_LEGACY_COMMANDS" is not empty,
|
|
||||||
// these legacy commands (such as `docker ps`, `docker exec`, etc)
|
|
||||||
// will not be shown in output console.
|
|
||||||
if os.Getenv("DOCKER_HIDE_LEGACY_COMMANDS") == "" {
|
|
||||||
return cmd
|
|
||||||
}
|
}
|
||||||
cmdCopy := *cmd
|
|
||||||
cmdCopy.Hidden = true
|
|
||||||
cmdCopy.Aliases = []string{}
|
|
||||||
return &cmdCopy
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,20 +5,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/moby/moby/client"
|
||||||
"github.com/docker/docker/api/types/network"
|
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidArgsFn a function to be used by cobra command as `ValidArgsFunction` to offer command line completion.
|
// APIClientProvider provides a method to get a [client.APIClient], initializing
|
||||||
//
|
|
||||||
// Deprecated: use [cobra.CompletionFunc].
|
|
||||||
type ValidArgsFn = cobra.CompletionFunc
|
|
||||||
|
|
||||||
// APIClientProvider provides a method to get an [client.APIClient], initializing
|
|
||||||
// it if needed.
|
// it if needed.
|
||||||
//
|
//
|
||||||
// It's a smaller interface than [command.Cli], and used in situations where an
|
// It's a smaller interface than [command.Cli], and used in situations where an
|
||||||
|
@ -34,7 +26,7 @@ func ImageNames(dockerCLI APIClientProvider, limit int) cobra.CompletionFunc {
|
||||||
if limit > 0 && len(args) >= limit {
|
if limit > 0 && len(args) >= limit {
|
||||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||||
}
|
}
|
||||||
list, err := dockerCLI.Client().ImageList(cmd.Context(), image.ListOptions{})
|
list, err := dockerCLI.Client().ImageList(cmd.Context(), client.ImageListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, cobra.ShellCompDirectiveError
|
return nil, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
@ -84,7 +76,7 @@ func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(conta
|
||||||
// VolumeNames offers completion for volumes
|
// VolumeNames offers completion for volumes
|
||||||
func VolumeNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
|
func VolumeNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
|
||||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
list, err := dockerCLI.Client().VolumeList(cmd.Context(), volume.ListOptions{})
|
list, err := dockerCLI.Client().VolumeList(cmd.Context(), client.VolumeListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, cobra.ShellCompDirectiveError
|
return nil, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
@ -99,7 +91,7 @@ func VolumeNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
|
||||||
// NetworkNames offers completion for networks
|
// NetworkNames offers completion for networks
|
||||||
func NetworkNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
|
func NetworkNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
|
||||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
list, err := dockerCLI.Client().NetworkList(cmd.Context(), network.ListOptions{})
|
list, err := dockerCLI.Client().NetworkList(cmd.Context(), client.NetworkListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, cobra.ShellCompDirectiveError
|
return nil, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
@ -146,11 +138,6 @@ 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
|
|
||||||
func NoComplete(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
|
||||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
|
||||||
}
|
|
||||||
|
|
||||||
var commonPlatforms = []string{
|
var commonPlatforms = []string{
|
||||||
"linux/386",
|
"linux/386",
|
||||||
"linux/amd64",
|
"linux/amd64",
|
||||||
|
|
|
@ -6,13 +6,13 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/docker/docker/api/types/image"
|
|
||||||
"github.com/docker/docker/api/types/network"
|
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"github.com/moby/moby/api/types/container"
|
||||||
|
"github.com/moby/moby/api/types/filters"
|
||||||
|
"github.com/moby/moby/api/types/image"
|
||||||
|
"github.com/moby/moby/api/types/network"
|
||||||
|
"github.com/moby/moby/api/types/volume"
|
||||||
|
"github.com/moby/moby/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
|
@ -31,8 +31,8 @@ func (c fakeCLI) Client() client.APIClient {
|
||||||
type fakeClient struct {
|
type fakeClient struct {
|
||||||
client.Client
|
client.Client
|
||||||
containerListFunc func(options container.ListOptions) ([]container.Summary, error)
|
containerListFunc func(options container.ListOptions) ([]container.Summary, error)
|
||||||
imageListFunc func(options image.ListOptions) ([]image.Summary, error)
|
imageListFunc func(options client.ImageListOptions) ([]image.Summary, error)
|
||||||
networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error)
|
networkListFunc func(ctx context.Context, options client.NetworkListOptions) ([]network.Summary, error)
|
||||||
volumeListFunc func(filter filters.Args) (volume.ListResponse, error)
|
volumeListFunc func(filter filters.Args) (volume.ListResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,21 +43,21 @@ func (c *fakeClient) ContainerList(_ context.Context, options container.ListOpti
|
||||||
return []container.Summary{}, nil
|
return []container.Summary{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeClient) ImageList(_ context.Context, options image.ListOptions) ([]image.Summary, error) {
|
func (c *fakeClient) ImageList(_ context.Context, options client.ImageListOptions) ([]image.Summary, error) {
|
||||||
if c.imageListFunc != nil {
|
if c.imageListFunc != nil {
|
||||||
return c.imageListFunc(options)
|
return c.imageListFunc(options)
|
||||||
}
|
}
|
||||||
return []image.Summary{}, nil
|
return []image.Summary{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeClient) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
|
func (c *fakeClient) NetworkList(ctx context.Context, options client.NetworkListOptions) ([]network.Summary, error) {
|
||||||
if c.networkListFunc != nil {
|
if c.networkListFunc != nil {
|
||||||
return c.networkListFunc(ctx, options)
|
return c.networkListFunc(ctx, options)
|
||||||
}
|
}
|
||||||
return []network.Inspect{}, nil
|
return []network.Inspect{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeClient) VolumeList(_ context.Context, options volume.ListOptions) (volume.ListResponse, error) {
|
func (c *fakeClient) VolumeList(_ context.Context, options client.VolumeListOptions) (volume.ListResponse, error) {
|
||||||
if c.volumeListFunc != nil {
|
if c.volumeListFunc != nil {
|
||||||
return c.volumeListFunc(options.Filters)
|
return c.volumeListFunc(options.Filters)
|
||||||
}
|
}
|
||||||
|
@ -228,7 +228,7 @@ func TestCompleteImageNames(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) {
|
||||||
comp := ImageNames(fakeCLI{&fakeClient{
|
comp := ImageNames(fakeCLI{&fakeClient{
|
||||||
imageListFunc: func(options image.ListOptions) ([]image.Summary, error) {
|
imageListFunc: func(options client.ImageListOptions) ([]image.Summary, error) {
|
||||||
if tc.expDirective == cobra.ShellCompDirectiveError {
|
if tc.expDirective == cobra.ShellCompDirectiveError {
|
||||||
return nil, errors.New("some error occurred")
|
return nil, errors.New("some error occurred")
|
||||||
}
|
}
|
||||||
|
@ -273,7 +273,7 @@ func TestCompleteNetworkNames(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) {
|
||||||
comp := NetworkNames(fakeCLI{&fakeClient{
|
comp := NetworkNames(fakeCLI{&fakeClient{
|
||||||
networkListFunc: func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
|
networkListFunc: func(ctx context.Context, options client.NetworkListOptions) ([]network.Summary, error) {
|
||||||
if tc.expDirective == cobra.ShellCompDirectiveError {
|
if tc.expDirective == cobra.ShellCompDirectiveError {
|
||||||
return nil, errors.New("some error occurred")
|
return nil, errors.New("some error occurred")
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
|
|
@ -3,15 +3,15 @@ package config
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/moby/moby/api/types/swarm"
|
||||||
"github.com/docker/docker/client"
|
"github.com/moby/moby/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeClient struct {
|
type fakeClient struct {
|
||||||
client.Client
|
client.Client
|
||||||
configCreateFunc func(context.Context, swarm.ConfigSpec) (swarm.ConfigCreateResponse, error)
|
configCreateFunc func(context.Context, swarm.ConfigSpec) (swarm.ConfigCreateResponse, error)
|
||||||
configInspectFunc func(context.Context, string) (swarm.Config, []byte, error)
|
configInspectFunc func(context.Context, string) (swarm.Config, []byte, error)
|
||||||
configListFunc func(context.Context, swarm.ConfigListOptions) ([]swarm.Config, error)
|
configListFunc func(context.Context, client.ConfigListOptions) ([]swarm.Config, error)
|
||||||
configRemoveFunc func(string) error
|
configRemoveFunc func(string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ func (c *fakeClient) ConfigInspectWithRaw(ctx context.Context, id string) (swarm
|
||||||
return swarm.Config{}, nil, nil
|
return swarm.Config{}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeClient) ConfigList(ctx context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
|
func (c *fakeClient) ConfigList(ctx context.Context, options client.ConfigListOptions) ([]swarm.Config, error) {
|
||||||
if c.configListFunc != nil {
|
if c.configListFunc != nil {
|
||||||
return c.configListFunc(ctx, options)
|
return c.configListFunc(ctx, options)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,27 +4,33 @@ 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/docker/api/types/swarm"
|
"github.com/docker/cli/internal/commands"
|
||||||
|
"github.com/moby/moby/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewConfigCommand returns a cobra command for `config` subcommands
|
func init() {
|
||||||
func NewConfigCommand(dockerCli command.Cli) *cobra.Command {
|
commands.Register(newConfigCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newConfigCommand returns a cobra command for `config` subcommands
|
||||||
|
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",
|
||||||
},
|
},
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
newConfigListCommand(dockerCli),
|
newConfigListCommand(dockerCLI),
|
||||||
newConfigCreateCommand(dockerCli),
|
newConfigCreateCommand(dockerCLI),
|
||||||
newConfigInspectCommand(dockerCli),
|
newConfigInspectCommand(dockerCLI),
|
||||||
newConfigRemoveCommand(dockerCli),
|
newConfigRemoveCommand(dockerCLI),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -32,7 +38,7 @@ func NewConfigCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
// completeNames offers completion for swarm configs
|
// completeNames offers completion for swarm configs
|
||||||
func completeNames(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
|
func completeNames(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
|
||||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
list, err := dockerCLI.Client().ConfigList(cmd.Context(), swarm.ConfigListOptions{})
|
list, err := dockerCLI.Client().ConfigList(cmd.Context(), client.ConfigListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, cobra.ShellCompDirectiveError
|
return nil, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,30 +2,29 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"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/moby/moby/api/types/swarm"
|
||||||
"github.com/moby/sys/sequential"
|
"github.com/moby/sys/sequential"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateOptions specifies some options that are used when creating a config.
|
// createOptions specifies some options that are used when creating a config.
|
||||||
type CreateOptions struct {
|
type createOptions struct {
|
||||||
Name string
|
name string
|
||||||
TemplateDriver string
|
templateDriver string
|
||||||
File string
|
file string
|
||||||
Labels opts.ListOpts
|
labels opts.ListOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command {
|
func newConfigCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
createOpts := CreateOptions{
|
createOpts := createOptions{
|
||||||
Labels: opts.NewListOpts(opts.ValidateLabel),
|
labels: opts.NewListOpts(opts.ValidateLabel),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -33,39 +32,40 @@ 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,
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
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.
|
// runCreate creates a config with the given options.
|
||||||
func RunConfigCreate(ctx context.Context, dockerCLI command.Cli, options CreateOptions) error {
|
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 fmt.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)
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/moby/moby/api/types/swarm"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/cli/cli/command/inspect"
|
"github.com/docker/cli/cli/command/inspect"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
|
"github.com/moby/moby/api/types/swarm"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -29,8 +29,8 @@ Data:
|
||||||
{{.Data}}`
|
{{.Data}}`
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewFormat returns a Format for rendering using a config Context
|
// newFormat returns a Format for rendering using a configContext.
|
||||||
func NewFormat(source string, quiet bool) formatter.Format {
|
func newFormat(source string, quiet bool) formatter.Format {
|
||||||
switch source {
|
switch source {
|
||||||
case formatter.PrettyFormatKey:
|
case formatter.PrettyFormatKey:
|
||||||
return configInspectPrettyTemplate
|
return configInspectPrettyTemplate
|
||||||
|
@ -43,9 +43,20 @@ func NewFormat(source string, quiet bool) formatter.Format {
|
||||||
return formatter.Format(source)
|
return formatter.Format(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatWrite writes the context
|
// formatWrite writes the context
|
||||||
func FormatWrite(ctx formatter.Context, configs []swarm.Config) error {
|
func formatWrite(fmtCtx formatter.Context, configs []swarm.Config) error {
|
||||||
render := func(format func(subContext formatter.SubContext) error) error {
|
cCtx := &configContext{
|
||||||
|
HeaderContext: formatter.HeaderContext{
|
||||||
|
Header: formatter.SubHeaderContext{
|
||||||
|
"ID": configIDHeader,
|
||||||
|
"Name": formatter.NameHeader,
|
||||||
|
"CreatedAt": configCreatedHeader,
|
||||||
|
"UpdatedAt": configUpdatedHeader,
|
||||||
|
"Labels": formatter.LabelsHeader,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return fmtCtx.Write(cCtx, func(format func(subContext formatter.SubContext) error) error {
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
configCtx := &configContext{c: config}
|
configCtx := &configContext{c: config}
|
||||||
if err := format(configCtx); err != nil {
|
if err := format(configCtx); err != nil {
|
||||||
|
@ -53,21 +64,7 @@ func FormatWrite(ctx formatter.Context, configs []swarm.Config) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
})
|
||||||
return ctx.Write(newConfigContext(), render)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConfigContext() *configContext {
|
|
||||||
cCtx := &configContext{}
|
|
||||||
|
|
||||||
cCtx.Header = formatter.SubHeaderContext{
|
|
||||||
"ID": configIDHeader,
|
|
||||||
"Name": formatter.NameHeader,
|
|
||||||
"CreatedAt": configCreatedHeader,
|
|
||||||
"UpdatedAt": configUpdatedHeader,
|
|
||||||
"Labels": formatter.LabelsHeader,
|
|
||||||
}
|
|
||||||
return cCtx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type configContext struct {
|
type configContext struct {
|
||||||
|
@ -114,12 +111,12 @@ func (c *configContext) Label(name string) string {
|
||||||
return c.c.Spec.Annotations.Labels[name]
|
return c.c.Spec.Annotations.Labels[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
func inspectFormatWrite(fmtCtx formatter.Context, refs []string, getRef inspect.GetRefFunc) error {
|
||||||
if ctx.Format != configInspectPrettyTemplate {
|
if fmtCtx.Format != configInspectPrettyTemplate {
|
||||||
return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef)
|
return inspect.Inspect(fmtCtx.Output, refs, string(fmtCtx.Format), getRef)
|
||||||
}
|
}
|
||||||
render := func(format func(subContext formatter.SubContext) error) error {
|
return fmtCtx.Write(&configInspectContext{}, func(format func(subContext formatter.SubContext) error) error {
|
||||||
for _, ref := range refs {
|
for _, ref := range refs {
|
||||||
configI, _, err := getRef(ref)
|
configI, _, err := getRef(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -134,8 +131,7 @@ func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.Get
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
})
|
||||||
return ctx.Write(&configInspectContext{}, render)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type configInspectContext struct {
|
type configInspectContext struct {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/moby/moby/api/types/swarm"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -15,58 +15,58 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InspectOptions contains options for the docker config inspect command.
|
// inspectOptions contains options for the docker config inspect command.
|
||||||
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 {
|
func newConfigInspectCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
opts := InspectOptions{}
|
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)
|
||||||
},
|
},
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// runInspect inspects the given Swarm config.
|
||||||
func RunConfigInspect(ctx context.Context, dockerCLI command.Cli, opts InspectOptions) error {
|
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
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/cli/internal/test/builders"
|
"github.com/docker/cli/internal/test/builders"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/moby/moby/api/types/swarm"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,24 +6,23 @@ 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"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
|
||||||
"github.com/fvbommel/sortorder"
|
"github.com/fvbommel/sortorder"
|
||||||
|
"github.com/moby/moby/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListOptions contains options for the docker config ls command.
|
// listOptions contains options for the docker config ls command.
|
||||||
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 {
|
func newConfigListCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
listOpts := ListOptions{Filter: opts.NewFilterOpt()}
|
listOpts := listOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls [OPTIONS]",
|
Use: "ls [OPTIONS]",
|
||||||
|
@ -31,31 +30,32 @@ 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,
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// runList lists Swarm configs.
|
||||||
func RunConfigList(ctx context.Context, dockerCLI command.Cli, options ListOptions) error {
|
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, client.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 +68,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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ import (
|
||||||
"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"
|
||||||
"github.com/docker/cli/internal/test/builders"
|
"github.com/docker/cli/internal/test/builders"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/moby/moby/api/types/swarm"
|
||||||
|
"github.com/moby/moby/client"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
|
@ -19,7 +20,7 @@ import (
|
||||||
func TestConfigListErrors(t *testing.T) {
|
func TestConfigListErrors(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
args []string
|
args []string
|
||||||
configListFunc func(context.Context, swarm.ConfigListOptions) ([]swarm.Config, error)
|
configListFunc func(context.Context, client.ConfigListOptions) ([]swarm.Config, error)
|
||||||
expectedError string
|
expectedError string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -27,7 +28,7 @@ func TestConfigListErrors(t *testing.T) {
|
||||||
expectedError: "accepts no argument",
|
expectedError: "accepts no argument",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
configListFunc: func(_ context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
|
configListFunc: func(_ context.Context, options client.ConfigListOptions) ([]swarm.Config, error) {
|
||||||
return []swarm.Config{}, errors.New("error listing configs")
|
return []swarm.Config{}, errors.New("error listing configs")
|
||||||
},
|
},
|
||||||
expectedError: "error listing configs",
|
expectedError: "error listing configs",
|
||||||
|
@ -48,7 +49,7 @@ func TestConfigListErrors(t *testing.T) {
|
||||||
|
|
||||||
func TestConfigList(t *testing.T) {
|
func TestConfigList(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
configListFunc: func(_ context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
|
configListFunc: func(_ context.Context, options client.ConfigListOptions) ([]swarm.Config, error) {
|
||||||
return []swarm.Config{
|
return []swarm.Config{
|
||||||
*builders.Config(builders.ConfigID("ID-1-foo"),
|
*builders.Config(builders.ConfigID("ID-1-foo"),
|
||||||
builders.ConfigName("1-foo"),
|
builders.ConfigName("1-foo"),
|
||||||
|
@ -78,7 +79,7 @@ func TestConfigList(t *testing.T) {
|
||||||
|
|
||||||
func TestConfigListWithQuietOption(t *testing.T) {
|
func TestConfigListWithQuietOption(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
configListFunc: func(_ context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
|
configListFunc: func(_ context.Context, options client.ConfigListOptions) ([]swarm.Config, error) {
|
||||||
return []swarm.Config{
|
return []swarm.Config{
|
||||||
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
|
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
|
||||||
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
|
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
|
||||||
|
@ -95,7 +96,7 @@ func TestConfigListWithQuietOption(t *testing.T) {
|
||||||
|
|
||||||
func TestConfigListWithConfigFormat(t *testing.T) {
|
func TestConfigListWithConfigFormat(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
configListFunc: func(_ context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
|
configListFunc: func(_ context.Context, options client.ConfigListOptions) ([]swarm.Config, error) {
|
||||||
return []swarm.Config{
|
return []swarm.Config{
|
||||||
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
|
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
|
||||||
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
|
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
|
||||||
|
@ -114,7 +115,7 @@ func TestConfigListWithConfigFormat(t *testing.T) {
|
||||||
|
|
||||||
func TestConfigListWithFormat(t *testing.T) {
|
func TestConfigListWithFormat(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
configListFunc: func(_ context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
|
configListFunc: func(_ context.Context, options client.ConfigListOptions) ([]swarm.Config, error) {
|
||||||
return []swarm.Config{
|
return []swarm.Config{
|
||||||
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
|
*builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")),
|
||||||
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
|
*builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{
|
||||||
|
@ -131,7 +132,7 @@ func TestConfigListWithFormat(t *testing.T) {
|
||||||
|
|
||||||
func TestConfigListWithFilter(t *testing.T) {
|
func TestConfigListWithFilter(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
configListFunc: func(_ context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
|
configListFunc: func(_ context.Context, options client.ConfigListOptions) ([]swarm.Config, error) {
|
||||||
assert.Check(t, is.Equal("foo", options.Filters.Get("name")[0]))
|
assert.Check(t, is.Equal("foo", options.Filters.Get("name")[0]))
|
||||||
assert.Check(t, is.Equal("lbl1=Label-bar", options.Filters.Get("label")[0]))
|
assert.Check(t, is.Equal("lbl1=Label-bar", options.Filters.Get("label")[0]))
|
||||||
return []swarm.Config{
|
return []swarm.Config{
|
||||||
|
|
|
@ -10,35 +10,28 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemoveOptions contains options for the docker config rm command.
|
func newConfigRemoveCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
type RemoveOptions struct {
|
|
||||||
Names []string
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
||||||
},
|
},
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunConfigRemove removes the given Swarm configs.
|
// runRemove removes the given Swarm configs.
|
||||||
func RunConfigRemove(ctx context.Context, dockerCLI command.Cli, opts RemoveOptions) error {
|
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
|
||||||
|
|
|
@ -2,15 +2,15 @@ package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"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/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/docker/docker/client"
|
"github.com/moby/moby/client"
|
||||||
"github.com/moby/sys/signal"
|
"github.com/moby/sys/signal"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -28,20 +28,20 @@ func inspectContainerAndCheckState(ctx context.Context, apiClient client.APIClie
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !c.State.Running {
|
if !c.State.Running {
|
||||||
return nil, errors.New("You cannot attach to a stopped container, start it first")
|
return nil, errors.New("cannot attach to a stopped container, start it first")
|
||||||
}
|
}
|
||||||
if c.State.Paused {
|
if c.State.Paused {
|
||||||
return nil, errors.New("You cannot attach to a paused container, unpause it first")
|
return nil, errors.New("cannot attach to a paused container, unpause it first")
|
||||||
}
|
}
|
||||||
if c.State.Restarting {
|
if c.State.Restarting {
|
||||||
return nil, errors.New("You cannot attach to a restarting container, wait until it is running")
|
return nil, errors.New("cannot attach to a restarting container, wait until it is running")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAttachCommand creates a new cobra.Command for `docker attach`
|
// newAttachCommand creates a new cobra.Command for `docker attach`
|
||||||
func NewAttachCommand(dockerCLI command.Cli) *cobra.Command {
|
func newAttachCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts AttachOptions
|
var opts AttachOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -58,6 +58,7 @@ func NewAttachCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
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
|
||||||
}),
|
}),
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,13 +29,11 @@ func TestNewAttachCommandErrors(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "client-stopped",
|
name: "client-stopped",
|
||||||
args: []string{"5cb5bb5e4a3b"},
|
args: []string{"5cb5bb5e4a3b"},
|
||||||
expectedError: "You cannot attach to a stopped container",
|
expectedError: "cannot attach to a stopped container",
|
||||||
containerInspectFunc: func(containerID string) (container.InspectResponse, error) {
|
containerInspectFunc: func(containerID string) (container.InspectResponse, error) {
|
||||||
return container.InspectResponse{
|
return container.InspectResponse{
|
||||||
ContainerJSONBase: &container.ContainerJSONBase{
|
State: &container.State{
|
||||||
State: &container.State{
|
Running: false,
|
||||||
Running: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
@ -43,14 +41,12 @@ func TestNewAttachCommandErrors(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "client-paused",
|
name: "client-paused",
|
||||||
args: []string{"5cb5bb5e4a3b"},
|
args: []string{"5cb5bb5e4a3b"},
|
||||||
expectedError: "You cannot attach to a paused container",
|
expectedError: "cannot attach to a paused container",
|
||||||
containerInspectFunc: func(containerID string) (container.InspectResponse, error) {
|
containerInspectFunc: func(containerID string) (container.InspectResponse, error) {
|
||||||
return container.InspectResponse{
|
return container.InspectResponse{
|
||||||
ContainerJSONBase: &container.ContainerJSONBase{
|
State: &container.State{
|
||||||
State: &container.State{
|
Running: true,
|
||||||
Running: true,
|
Paused: true,
|
||||||
Paused: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
@ -58,15 +54,13 @@ func TestNewAttachCommandErrors(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "client-restarting",
|
name: "client-restarting",
|
||||||
args: []string{"5cb5bb5e4a3b"},
|
args: []string{"5cb5bb5e4a3b"},
|
||||||
expectedError: "You cannot attach to a restarting container",
|
expectedError: "cannot attach to a restarting container",
|
||||||
containerInspectFunc: func(containerID string) (container.InspectResponse, error) {
|
containerInspectFunc: func(containerID string) (container.InspectResponse, error) {
|
||||||
return container.InspectResponse{
|
return container.InspectResponse{
|
||||||
ContainerJSONBase: &container.ContainerJSONBase{
|
State: &container.State{
|
||||||
State: &container.State{
|
Running: true,
|
||||||
Running: true,
|
Paused: false,
|
||||||
Paused: false,
|
Restarting: true,
|
||||||
Restarting: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
@ -74,7 +68,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)
|
||||||
|
|
|
@ -4,13 +4,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/moby/moby/api/types/network"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/moby/moby/api/types/system"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/moby/moby/client"
|
||||||
"github.com/docker/docker/api/types/system"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,7 +23,7 @@ type fakeClient struct {
|
||||||
platform *ocispec.Platform,
|
platform *ocispec.Platform,
|
||||||
containerName string) (container.CreateResponse, error)
|
containerName string) (container.CreateResponse, error)
|
||||||
containerStartFunc func(containerID string, options container.StartOptions) error
|
containerStartFunc func(containerID string, options container.StartOptions) error
|
||||||
imageCreateFunc func(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error)
|
imageCreateFunc func(ctx context.Context, parentReference string, options client.ImageCreateOptions) (io.ReadCloser, error)
|
||||||
infoFunc func() (system.Info, error)
|
infoFunc func() (system.Info, error)
|
||||||
containerStatPathFunc func(containerID, path string) (container.PathStat, error)
|
containerStatPathFunc func(containerID, path string) (container.PathStat, error)
|
||||||
containerCopyFromFunc func(containerID, srcPath string) (io.ReadCloser, container.PathStat, error)
|
containerCopyFromFunc func(containerID, srcPath string) (io.ReadCloser, container.PathStat, error)
|
||||||
|
@ -33,13 +31,13 @@ type fakeClient struct {
|
||||||
waitFunc func(string) (<-chan container.WaitResponse, <-chan error)
|
waitFunc func(string) (<-chan container.WaitResponse, <-chan error)
|
||||||
containerListFunc func(container.ListOptions) ([]container.Summary, error)
|
containerListFunc func(container.ListOptions) ([]container.Summary, error)
|
||||||
containerExportFunc func(string) (io.ReadCloser, error)
|
containerExportFunc func(string) (io.ReadCloser, error)
|
||||||
containerExecResizeFunc func(id string, options container.ResizeOptions) error
|
containerExecResizeFunc func(id string, options client.ContainerResizeOptions) error
|
||||||
containerRemoveFunc func(ctx context.Context, containerID string, options container.RemoveOptions) error
|
containerRemoveFunc func(ctx context.Context, containerID string, options container.RemoveOptions) error
|
||||||
containerRestartFunc func(ctx context.Context, containerID string, options container.StopOptions) error
|
containerRestartFunc func(ctx context.Context, containerID string, options container.StopOptions) error
|
||||||
containerStopFunc func(ctx context.Context, containerID string, options container.StopOptions) error
|
containerStopFunc func(ctx context.Context, containerID string, options container.StopOptions) error
|
||||||
containerKillFunc func(ctx context.Context, containerID, signal string) error
|
containerKillFunc func(ctx context.Context, containerID, signal string) error
|
||||||
containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error)
|
containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error)
|
||||||
containerAttachFunc func(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error)
|
containerAttachFunc func(ctx context.Context, containerID string, options container.AttachOptions) (client.HijackedResponse, error)
|
||||||
containerDiffFunc func(ctx context.Context, containerID string) ([]container.FilesystemChange, error)
|
containerDiffFunc func(ctx context.Context, containerID string) ([]container.FilesystemChange, error)
|
||||||
containerRenameFunc func(ctx context.Context, oldName, newName string) error
|
containerRenameFunc func(ctx context.Context, oldName, newName string) error
|
||||||
containerCommitFunc func(ctx context.Context, container string, options container.CommitOptions) (container.CommitResponse, error)
|
containerCommitFunc func(ctx context.Context, container string, options container.CommitOptions) (container.CommitResponse, error)
|
||||||
|
@ -100,7 +98,7 @@ func (f *fakeClient) ContainerRemove(ctx context.Context, containerID string, op
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeClient) ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
|
func (f *fakeClient) ImageCreate(ctx context.Context, parentReference string, options client.ImageCreateOptions) (io.ReadCloser, error) {
|
||||||
if f.imageCreateFunc != nil {
|
if f.imageCreateFunc != nil {
|
||||||
return f.imageCreateFunc(ctx, parentReference, options)
|
return f.imageCreateFunc(ctx, parentReference, options)
|
||||||
}
|
}
|
||||||
|
@ -160,7 +158,7 @@ func (f *fakeClient) ContainerExport(_ context.Context, containerID string) (io.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeClient) ContainerExecResize(_ context.Context, id string, options container.ResizeOptions) error {
|
func (f *fakeClient) ContainerExecResize(_ context.Context, id string, options client.ContainerResizeOptions) error {
|
||||||
if f.containerExecResizeFunc != nil {
|
if f.containerExecResizeFunc != nil {
|
||||||
return f.containerExecResizeFunc(id, options)
|
return f.containerExecResizeFunc(id, options)
|
||||||
}
|
}
|
||||||
|
@ -195,11 +193,11 @@ func (f *fakeClient) ContainerStop(ctx context.Context, containerID string, opti
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeClient) ContainerAttach(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
|
func (f *fakeClient) ContainerAttach(ctx context.Context, containerID string, options container.AttachOptions) (client.HijackedResponse, error) {
|
||||||
if f.containerAttachFunc != nil {
|
if f.containerAttachFunc != nil {
|
||||||
return f.containerAttachFunc(ctx, containerID, options)
|
return f.containerAttachFunc(ctx, containerID, options)
|
||||||
}
|
}
|
||||||
return types.HijackedResponse{}, nil
|
return client.HijackedResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeClient) ContainerDiff(ctx context.Context, containerID string) ([]container.FilesystemChange, error) {
|
func (f *fakeClient) ContainerDiff(ctx context.Context, containerID string) ([]container.FilesystemChange, error) {
|
||||||
|
|
|
@ -3,43 +3,73 @@ package container
|
||||||
import (
|
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/internal/commands"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewContainerCommand returns a cobra command for `container` subcommands
|
func init() {
|
||||||
func NewContainerCommand(dockerCli command.Cli) *cobra.Command {
|
commands.Register(newRunCommand)
|
||||||
|
commands.Register(newExecCommand)
|
||||||
|
commands.Register(newPsCommand)
|
||||||
|
commands.Register(newContainerCommand)
|
||||||
|
commands.RegisterLegacy(newAttachCommand)
|
||||||
|
commands.RegisterLegacy(newCommitCommand)
|
||||||
|
commands.RegisterLegacy(newCopyCommand)
|
||||||
|
commands.RegisterLegacy(newCreateCommand)
|
||||||
|
commands.RegisterLegacy(newDiffCommand)
|
||||||
|
commands.RegisterLegacy(newExportCommand)
|
||||||
|
commands.RegisterLegacy(newKillCommand)
|
||||||
|
commands.RegisterLegacy(newLogsCommand)
|
||||||
|
commands.RegisterLegacy(newPauseCommand)
|
||||||
|
commands.RegisterLegacy(newPortCommand)
|
||||||
|
commands.RegisterLegacy(newRenameCommand)
|
||||||
|
commands.RegisterLegacy(newRestartCommand)
|
||||||
|
commands.RegisterLegacy(newRmCommand)
|
||||||
|
commands.RegisterLegacy(newStartCommand)
|
||||||
|
commands.RegisterLegacy(newStatsCommand)
|
||||||
|
commands.RegisterLegacy(newStopCommand)
|
||||||
|
commands.RegisterLegacy(newTopCommand)
|
||||||
|
commands.RegisterLegacy(newUnpauseCommand)
|
||||||
|
commands.RegisterLegacy(newUpdateCommand)
|
||||||
|
commands.RegisterLegacy(newWaitCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newContainerCommand returns a cobra command for `container` subcommands
|
||||||
|
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()),
|
||||||
|
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"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/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ type commitOptions struct {
|
||||||
changes opts.ListOpts
|
changes opts.ListOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
func newCommitCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var options commitOptions
|
var options commitOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -35,12 +35,13 @@ 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),
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
)
|
)
|
||||||
|
@ -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"})
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/completion"
|
"github.com/docker/cli/cli/command/completion"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/moby/sys/capability"
|
"github.com/moby/sys/capability"
|
||||||
"github.com/moby/sys/signal"
|
"github.com/moby/sys/signal"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/cli/internal/test/builders"
|
"github.com/docker/cli/internal/test/builders"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/moby/sys/signal"
|
"github.com/moby/sys/signal"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
|
@ -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))
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,6 +3,7 @@ package container
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -15,11 +16,10 @@ 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/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/moby/go-archive"
|
"github.com/moby/go-archive"
|
||||||
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/morikuni/aec"
|
"github.com/morikuni/aec"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -121,8 +121,8 @@ func copyProgress(ctx context.Context, dst io.Writer, header string, total *int6
|
||||||
return restore, done
|
return restore, done
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCopyCommand creates a new `docker cp` command
|
// newCopyCommand creates a new `docker cp` command
|
||||||
func NewCopyCommand(dockerCli command.Cli) *cobra.Command {
|
func newCopyCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts copyOptions
|
var opts copyOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -147,13 +147,14 @@ 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",
|
||||||
},
|
},
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
@ -298,50 +299,50 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
|
||||||
// about both the source and destination. The API is a simple tar
|
// about both the source and destination. The API is a simple tar
|
||||||
// archive/extract API but we can use the stat info header about the
|
// archive/extract API but we can use the stat info header about the
|
||||||
// destination to be more informed about exactly what the destination is.
|
// destination to be more informed about exactly what the destination is.
|
||||||
func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpConfig) (err error) {
|
func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpConfig) error {
|
||||||
srcPath := copyConfig.sourcePath
|
srcPath := copyConfig.sourcePath
|
||||||
dstPath := copyConfig.destPath
|
dstPath := copyConfig.destPath
|
||||||
|
|
||||||
if srcPath != "-" {
|
if srcPath != "-" {
|
||||||
// Get an absolute source path.
|
// Get an absolute source path.
|
||||||
srcPath, err = resolveLocalPath(srcPath)
|
p, err := resolveLocalPath(srcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
srcPath = p
|
||||||
}
|
}
|
||||||
|
|
||||||
apiClient := dockerCLI.Client()
|
apiClient := dockerCLI.Client()
|
||||||
// Prepare destination copy info by stat-ing the container path.
|
// Prepare destination copy info by stat-ing the container path.
|
||||||
dstInfo := archive.CopyInfo{Path: dstPath}
|
dstInfo := archive.CopyInfo{Path: dstPath}
|
||||||
dstStat, err := apiClient.ContainerStatPath(ctx, copyConfig.container, dstPath)
|
if dstStat, err := apiClient.ContainerStatPath(ctx, copyConfig.container, dstPath); err == nil {
|
||||||
|
// If the destination is a symbolic link, we should evaluate it.
|
||||||
|
if dstStat.Mode&os.ModeSymlink != 0 {
|
||||||
|
linkTarget := dstStat.LinkTarget
|
||||||
|
if !isAbs(linkTarget) {
|
||||||
|
// Join with the parent directory.
|
||||||
|
dstParent, _ := archive.SplitPathDirEntry(dstPath)
|
||||||
|
linkTarget = filepath.Join(dstParent, linkTarget)
|
||||||
|
}
|
||||||
|
|
||||||
// If the destination is a symbolic link, we should evaluate it.
|
dstInfo.Path = linkTarget
|
||||||
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
|
dstStat, err = apiClient.ContainerStatPath(ctx, copyConfig.container, linkTarget)
|
||||||
linkTarget := dstStat.LinkTarget
|
}
|
||||||
if !isAbs(linkTarget) {
|
// Validate the destination path
|
||||||
// Join with the parent directory.
|
if err == nil {
|
||||||
dstParent, _ := archive.SplitPathDirEntry(dstPath)
|
if err := command.ValidateOutputPathFileMode(dstStat.Mode); err != nil {
|
||||||
linkTarget = filepath.Join(dstParent, linkTarget)
|
return fmt.Errorf(`destination "%s:%s" must be a directory or a regular file: %w`, copyConfig.container, dstPath, err)
|
||||||
|
}
|
||||||
|
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
dstInfo.Path = linkTarget
|
// Ignore any error and assume that the parent directory of the destination
|
||||||
dstStat, err = apiClient.ContainerStatPath(ctx, copyConfig.container, linkTarget)
|
// path exists, in which case the copy may still succeed. If there is any
|
||||||
// FIXME(thaJeztah): unhandled error (should this return?)
|
// type of conflict (e.g., non-directory overwriting an existing directory
|
||||||
}
|
// or vice versa) the extraction will fail. If the destination simply did
|
||||||
|
// not exist, but the parent directory does, the extraction will still
|
||||||
// Validate the destination path
|
// succeed.
|
||||||
if err := command.ValidateOutputPathFileMode(dstStat.Mode); err != nil {
|
_ = err // Intentionally ignore stat errors (see above)
|
||||||
return errors.Wrapf(err, `destination "%s:%s" must be a directory or a regular file`, copyConfig.container, dstPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore any error and assume that the parent directory of the destination
|
|
||||||
// path exists, in which case the copy may still succeed. If there is any
|
|
||||||
// type of conflict (e.g., non-directory overwriting an existing directory
|
|
||||||
// or vice versa) the extraction will fail. If the destination simply did
|
|
||||||
// not exist, but the parent directory does, the extraction will still
|
|
||||||
// succeed.
|
|
||||||
if err == nil {
|
|
||||||
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -354,7 +355,7 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
|
||||||
content = os.Stdin
|
content = os.Stdin
|
||||||
resolvedDstPath = dstInfo.Path
|
resolvedDstPath = dstInfo.Path
|
||||||
if !dstInfo.IsDir {
|
if !dstInfo.IsDir {
|
||||||
return errors.Errorf("destination \"%s:%s\" must be a directory", copyConfig.container, dstPath)
|
return fmt.Errorf(`destination "%s:%s" must be a directory`, copyConfig.container, dstPath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Prepare source copy info.
|
// Prepare source copy info.
|
||||||
|
@ -411,7 +412,7 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
|
||||||
cancel()
|
cancel()
|
||||||
<-done
|
<-done
|
||||||
restore()
|
restore()
|
||||||
fmt.Fprintln(dockerCLI.Err(), "Successfully copied", progressHumanSize(copiedSize), "to", copyConfig.container+":"+dstInfo.Path)
|
_, _ = fmt.Fprintln(dockerCLI.Err(), "Successfully copied", progressHumanSize(copiedSize), "to", copyConfig.container+":"+dstInfo.Path)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/moby/go-archive"
|
"github.com/moby/go-archive"
|
||||||
"github.com/moby/go-archive/compression"
|
"github.com/moby/go-archive/compression"
|
||||||
|
"github.com/moby/moby/api/types/container"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
"gotest.tools/v3/fs"
|
"gotest.tools/v3/fs"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
@ -11,7 +12,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"
|
||||||
|
@ -24,13 +25,11 @@ import (
|
||||||
"github.com/docker/cli/cli/trust"
|
"github.com/docker/cli/cli/trust"
|
||||||
"github.com/docker/cli/internal/jsonstream"
|
"github.com/docker/cli/internal/jsonstream"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
imagetypes "github.com/docker/docker/api/types/image"
|
"github.com/moby/moby/api/types/mount"
|
||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/moby/moby/api/types/versions"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/moby/moby/client"
|
||||||
"github.com/docker/docker/client"
|
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
@ -51,8 +50,8 @@ type createOptions struct {
|
||||||
useAPISocket bool
|
useAPISocket bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var options createOptions
|
var options createOptions
|
||||||
var copts *containerOptions
|
var copts *containerOptions
|
||||||
|
|
||||||
|
@ -65,12 +64,13 @@ 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),
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
@ -80,23 +80,26 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
flags.StringVar(&options.pull, "pull", PullImageMissing, `Pull image before creating ("`+PullImageAlways+`", "|`+PullImageMissing+`", "`+PullImageNever+`")`)
|
flags.StringVar(&options.pull, "pull", PullImageMissing, `Pull image before creating ("`+PullImageAlways+`", "|`+PullImageMissing+`", "`+PullImageNever+`")`)
|
||||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the pull output")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the pull output")
|
||||||
flags.BoolVarP(&options.useAPISocket, "use-api-socket", "", false, "Bind mount Docker API socket and required auth")
|
flags.BoolVarP(&options.useAPISocket, "use-api-socket", "", false, "Bind mount Docker API socket and required auth")
|
||||||
flags.SetAnnotation("use-api-socket", "experimentalCLI", nil) // Marks flag as experimental for now.
|
_ = flags.SetAnnotation("use-api-socket", "experimentalCLI", nil) // Mark flag as experimental for now.
|
||||||
|
|
||||||
// Add an explicit help that doesn't have a `-h` to prevent the conflict
|
// Add an explicit help that doesn't have a `-h` to prevent the conflict
|
||||||
// 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", !dockerCLI.ContentTrustEnabled(), "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
|
||||||
|
@ -110,7 +113,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
|
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
|
||||||
newEnv := []string{}
|
newEnv := make([]string, 0, len(proxyConfig))
|
||||||
for k, v := range proxyConfig {
|
for k, v := range proxyConfig {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
newEnv = append(newEnv, k)
|
newEnv = append(newEnv, k)
|
||||||
|
@ -141,14 +144,16 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
responseBody, err := dockerCli.Client().ImageCreate(ctx, img, imagetypes.CreateOptions{
|
responseBody, err := dockerCli.Client().ImageCreate(ctx, img, client.ImageCreateOptions{
|
||||||
RegistryAuth: encodedAuth,
|
RegistryAuth: encodedAuth,
|
||||||
Platform: options.platform,
|
Platform: options.platform,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer responseBody.Close()
|
defer func() {
|
||||||
|
_ = responseBody.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
out := dockerCli.Err()
|
out := dockerCli.Err()
|
||||||
if options.quiet {
|
if options.quiet {
|
||||||
|
@ -167,13 +172,13 @@ func (cid *cidFile) Close() error {
|
||||||
if cid.file == nil {
|
if cid.file == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cid.file.Close()
|
_ = cid.file.Close()
|
||||||
|
|
||||||
if cid.written {
|
if cid.written {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := os.Remove(cid.path); err != nil {
|
if err := os.Remove(cid.path); err != nil {
|
||||||
return errors.Wrapf(err, "failed to remove the CID file '%s'", cid.path)
|
return fmt.Errorf("failed to remove the CID file '%s': %w", cid.path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -184,7 +189,7 @@ func (cid *cidFile) Write(id string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if _, err := cid.file.Write([]byte(id)); err != nil {
|
if _, err := cid.file.Write([]byte(id)); err != nil {
|
||||||
return errors.Wrap(err, "failed to write the container ID to the file")
|
return fmt.Errorf("failed to write the container ID to the file: %w", err)
|
||||||
}
|
}
|
||||||
cid.written = true
|
cid.written = true
|
||||||
return nil
|
return nil
|
||||||
|
@ -195,12 +200,12 @@ func newCIDFile(cidPath string) (*cidFile, error) {
|
||||||
return &cidFile{}, nil
|
return &cidFile{}, nil
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(cidPath); err == nil {
|
if _, err := os.Stat(cidPath); err == nil {
|
||||||
return nil, errors.Errorf("container ID file found, make sure the other container isn't running or delete %s", cidPath)
|
return nil, errors.New("container ID file found, make sure the other container isn't running or delete " + cidPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Create(cidPath)
|
f, err := os.Create(cidPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to create the container ID file")
|
return nil, fmt.Errorf("failed to create the container ID file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cidFile{path: cidPath, file: f}, nil
|
return &cidFile{path: cidPath, file: f}, nil
|
||||||
|
@ -221,7 +226,9 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer containerIDFile.Close()
|
defer func() {
|
||||||
|
_ = containerIDFile.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
ref, err := reference.ParseAnyReference(config.Image)
|
ref, err := reference.ParseAnyReference(config.Image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -315,7 +322,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
|
||||||
if options.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") {
|
if options.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") {
|
||||||
p, err := platforms.Parse(options.platform)
|
p, err := platforms.Parse(options.platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(invalidParameter(err), "error parsing specified platform")
|
return "", invalidParameter(fmt.Errorf("error parsing specified platform: %w", err))
|
||||||
}
|
}
|
||||||
platform = &p
|
platform = &p
|
||||||
}
|
}
|
||||||
|
@ -341,7 +348,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))
|
||||||
|
@ -428,7 +435,7 @@ func copyDockerConfigIntoContainer(ctx context.Context, dockerAPI client.APIClie
|
||||||
// We don't need to get super fancy with the tar creation.
|
// We don't need to get super fancy with the tar creation.
|
||||||
var tarBuf bytes.Buffer
|
var tarBuf bytes.Buffer
|
||||||
tarWriter := tar.NewWriter(&tarBuf)
|
tarWriter := tar.NewWriter(&tarBuf)
|
||||||
tarWriter.WriteHeader(&tar.Header{
|
_ = tarWriter.WriteHeader(&tar.Header{
|
||||||
Name: configPath,
|
Name: configPath,
|
||||||
Size: int64(configBuf.Len()),
|
Size: int64(configBuf.Len()),
|
||||||
Mode: 0o600,
|
Mode: 0o600,
|
||||||
|
|
|
@ -14,11 +14,11 @@ import (
|
||||||
"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"
|
||||||
"github.com/docker/cli/internal/test/notary"
|
"github.com/docker/cli/internal/test/notary"
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/api/types/image"
|
|
||||||
"github.com/docker/docker/api/types/network"
|
|
||||||
"github.com/docker/docker/api/types/system"
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/moby/moby/api/types/container"
|
||||||
|
"github.com/moby/moby/api/types/network"
|
||||||
|
"github.com/moby/moby/api/types/system"
|
||||||
|
"github.com/moby/moby/client"
|
||||||
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"
|
||||||
|
@ -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,
|
||||||
|
@ -132,7 +132,7 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
|
||||||
return container.CreateResponse{ID: containerID}, nil
|
return container.CreateResponse{ID: containerID}, nil
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
imageCreateFunc: func(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
|
imageCreateFunc: func(ctx context.Context, parentReference string, options client.ImageCreateOptions) (io.ReadCloser, error) {
|
||||||
defer func() { pullCounter++ }()
|
defer func() { pullCounter++ }()
|
||||||
return io.NopCloser(strings.NewReader("")), nil
|
return io.NopCloser(strings.NewReader("")), nil
|
||||||
},
|
},
|
||||||
|
@ -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)
|
||||||
|
@ -260,7 +260,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, test.EnableContentTrust)
|
}, 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 +314,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 +366,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()
|
||||||
|
|
|
@ -10,19 +10,20 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 {
|
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),
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +34,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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
)
|
)
|
||||||
|
@ -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}
|
||||||
|
|
|
@ -10,8 +10,8 @@ import (
|
||||||
"github.com/docker/cli/cli/command/completion"
|
"github.com/docker/cli/cli/command/completion"
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/docker/docker/client"
|
"github.com/moby/moby/client"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -39,8 +39,8 @@ 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 {
|
func newExecCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
options := NewExecOptions()
|
options := NewExecOptions()
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -50,15 +50,16 @@ 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{
|
||||||
"category-top": "2",
|
"category-top": "2",
|
||||||
"aliases": "docker container exec, docker exec",
|
"aliases": "docker container exec, docker exec",
|
||||||
},
|
},
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"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"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
"gotest.tools/v3/fs"
|
"gotest.tools/v3/fs"
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -17,8 +17,8 @@ type exportOptions struct {
|
||||||
output string
|
output string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExportCommand creates a new `docker export` command
|
// newExportCommand creates a new "docker container export" command.
|
||||||
func NewExportCommand(dockerCli command.Cli) *cobra.Command {
|
func newExportCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts exportOptions
|
var opts exportOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -27,12 +27,13 @@ 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),
|
||||||
|
DisableFlagsInUseLine: 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"})
|
||||||
|
|
|
@ -2,7 +2,7 @@ package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -12,25 +12,24 @@ const (
|
||||||
pathHeader = "PATH"
|
pathHeader = "PATH"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewDiffFormat returns a format for use with a diff Context
|
// newDiffFormat returns a format for use with a diff [formatter.Context].
|
||||||
func NewDiffFormat(source string) formatter.Format {
|
func newDiffFormat(source string) formatter.Format {
|
||||||
if source == formatter.TableFormatKey {
|
if source == formatter.TableFormatKey {
|
||||||
return defaultDiffTableFormat
|
return defaultDiffTableFormat
|
||||||
}
|
}
|
||||||
return formatter.Format(source)
|
return formatter.Format(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiffFormatWrite writes formatted diff using the Context
|
// diffFormatWrite writes formatted diff using the [formatter.Context].
|
||||||
func DiffFormatWrite(ctx formatter.Context, changes []container.FilesystemChange) error {
|
func diffFormatWrite(fmtCtx formatter.Context, changes []container.FilesystemChange) error {
|
||||||
render := func(format func(subContext formatter.SubContext) error) 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 +38,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) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/moby/moby/api/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/moby/moby/client"
|
||||||
"github.com/moby/term"
|
"github.com/moby/term"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
@ -38,7 +38,7 @@ type hijackedIOStreamer struct {
|
||||||
outputStream io.Writer
|
outputStream io.Writer
|
||||||
errorStream io.Writer
|
errorStream io.Writer
|
||||||
|
|
||||||
resp types.HijackedResponse
|
resp client.HijackedResponse
|
||||||
|
|
||||||
tty bool
|
tty bool
|
||||||
detachKeys string
|
detachKeys string
|
||||||
|
|
|
@ -21,7 +21,7 @@ type inspectOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newInspectCommand creates a new cobra.Command for `docker container inspect`
|
// newInspectCommand creates a new cobra.Command for `docker container inspect`
|
||||||
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
func newInspectCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts inspectOptions
|
var opts inspectOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -30,9 +30,10 @@ func newInspectCommand(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.refs = args
|
opts.refs = args
|
||||||
return runInspect(cmd.Context(), dockerCli, opts)
|
return runInspect(cmd.Context(), dockerCLI, opts)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, true),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, true),
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
|
@ -17,8 +17,8 @@ type killOptions struct {
|
||||||
containers []string
|
containers []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKillCommand creates a new cobra.Command for `docker kill`
|
// newKillCommand creates a new cobra.Command for "docker container kill"
|
||||||
func NewKillCommand(dockerCli command.Cli) *cobra.Command {
|
func newKillCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts killOptions
|
var opts killOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -27,12 +27,13 @@ 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),
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
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,12 +6,11 @@ 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"
|
||||||
"github.com/docker/cli/templates"
|
"github.com/docker/cli/templates"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -28,8 +27,8 @@ type psOptions struct {
|
||||||
filter opts.FilterOpt
|
filter opts.FilterOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPsCommand creates a new cobra.Command for `docker ps`
|
// newPsCommand creates a new cobra.Command for "docker container ps"
|
||||||
func NewPsCommand(dockerCLI command.Cli) *cobra.Command {
|
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 +43,8 @@ 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,
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
@ -62,7 +62,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 +82,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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/cli/internal/test/builders"
|
"github.com/docker/cli/internal/test/builders"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
|
|
|
@ -7,8 +7,8 @@ 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/docker/api/types/container"
|
"github.com/moby/moby/api/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@ type logsOptions struct {
|
||||||
container string
|
container string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLogsCommand creates a new cobra.Command for `docker logs`
|
// newLogsCommand creates a new cobra.Command for "docker container logs"
|
||||||
func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
|
func newLogsCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts logsOptions
|
var opts logsOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -33,12 +33,13 @@ 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),
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
)
|
)
|
||||||
|
@ -21,8 +21,8 @@ var logFn = func(expectedOut string) func(string, container.LogsOptions) (io.Rea
|
||||||
func TestRunLogs(t *testing.T) {
|
func TestRunLogs(t *testing.T) {
|
||||||
inspectFn := func(containerID string) (container.InspectResponse, error) {
|
inspectFn := func(containerID string) (container.InspectResponse, error) {
|
||||||
return container.InspectResponse{
|
return container.InspectResponse{
|
||||||
Config: &container.Config{Tty: true},
|
Config: &container.Config{Tty: true},
|
||||||
ContainerJSONBase: &container.ContainerJSONBase{State: &container.State{Running: false}},
|
State: &container.State{Running: false},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,13 +12,13 @@ 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"
|
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
|
||||||
networktypes "github.com/docker/docker/api/types/network"
|
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
|
"github.com/moby/moby/api/types/container"
|
||||||
|
"github.com/moby/moby/api/types/mount"
|
||||||
|
"github.com/moby/moby/api/types/network"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
cdi "tags.cncf.io/container-device-interface/pkg/parser"
|
cdi "tags.cncf.io/container-device-interface/pkg/parser"
|
||||||
|
@ -141,6 +141,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{
|
||||||
|
@ -323,7 +333,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
|
||||||
type containerConfig struct {
|
type containerConfig struct {
|
||||||
Config *container.Config
|
Config *container.Config
|
||||||
HostConfig *container.HostConfig
|
HostConfig *container.HostConfig
|
||||||
NetworkingConfig *networktypes.NetworkingConfig
|
NetworkingConfig *network.NetworkingConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse parses the args for the specified command and generates a Config,
|
// parse parses the args for the specified command and generates a Config,
|
||||||
|
@ -364,7 +374,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
|
||||||
}
|
}
|
||||||
|
@ -372,7 +382,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||||
if parsed.Source != "" {
|
if parsed.Source != "" {
|
||||||
toBind := bind
|
toBind := bind
|
||||||
|
|
||||||
if parsed.Type == string(mounttypes.TypeBind) {
|
if parsed.Type == string(mount.TypeBind) {
|
||||||
if hostPart, targetPath, ok := strings.Cut(bind, ":"); ok {
|
if hostPart, targetPath, ok := strings.Cut(bind, ":"); ok {
|
||||||
if !filepath.IsAbs(hostPart) && strings.HasPrefix(hostPart, ".") {
|
if !filepath.IsAbs(hostPart) && strings.HasPrefix(hostPart, ".") {
|
||||||
if absHostPart, err := filepath.Abs(hostPart); err == nil {
|
if absHostPart, err := filepath.Abs(hostPart); err == nil {
|
||||||
|
@ -414,8 +424,8 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||||
|
|
||||||
publishOpts := copts.publish.GetSlice()
|
publishOpts := copts.publish.GetSlice()
|
||||||
var (
|
var (
|
||||||
ports map[nat.Port]struct{}
|
ports map[container.PortRangeProto]struct{}
|
||||||
portBindings map[nat.Port][]nat.PortBinding
|
portBindings map[container.PortRangeProto][]container.PortBinding
|
||||||
convertedOpts []string
|
convertedOpts []string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -706,8 +716,8 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||||
config.StdinOnce = true
|
config.StdinOnce = true
|
||||||
}
|
}
|
||||||
|
|
||||||
networkingConfig := &networktypes.NetworkingConfig{
|
networkingConfig := &network.NetworkingConfig{
|
||||||
EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
|
EndpointsConfig: make(map[string]*network.EndpointSettings),
|
||||||
}
|
}
|
||||||
|
|
||||||
networkingConfig.EndpointsConfig, err = parseNetworkOpts(copts)
|
networkingConfig.EndpointsConfig, err = parseNetworkOpts(copts)
|
||||||
|
@ -735,9 +745,9 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||||
// this function may return _multiple_ endpoints, which is not currently supported
|
// this function may return _multiple_ endpoints, which is not currently supported
|
||||||
// by the daemon, but may be in future; it's up to the daemon to produce an error
|
// by the daemon, but may be in future; it's up to the daemon to produce an error
|
||||||
// in case that is not supported.
|
// in case that is not supported.
|
||||||
func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.EndpointSettings, error) {
|
func parseNetworkOpts(copts *containerOptions) (map[string]*network.EndpointSettings, error) {
|
||||||
var (
|
var (
|
||||||
endpoints = make(map[string]*networktypes.EndpointSettings, len(copts.netMode.Value()))
|
endpoints = make(map[string]*network.EndpointSettings, len(copts.netMode.Value()))
|
||||||
hasUserDefined, hasNonUserDefined bool
|
hasUserDefined, hasNonUserDefined bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -783,7 +793,7 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin
|
||||||
// and only a single network is specified, omit the endpoint-configuration
|
// and only a single network is specified, omit the endpoint-configuration
|
||||||
// on the client (the daemon will still create it when creating the container)
|
// on the client (the daemon will still create it when creating the container)
|
||||||
if i == 0 && len(copts.netMode.Value()) == 1 {
|
if i == 0 && len(copts.netMode.Value()) == 1 {
|
||||||
if ep == nil || reflect.DeepEqual(*ep, networktypes.EndpointSettings{}) {
|
if ep == nil || reflect.DeepEqual(*ep, network.EndpointSettings{}) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -841,7 +851,7 @@ func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOption
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.EndpointSettings, error) {
|
func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*network.EndpointSettings, error) {
|
||||||
if strings.TrimSpace(ep.Target) == "" {
|
if strings.TrimSpace(ep.Target) == "" {
|
||||||
return nil, errors.New("no name set for network")
|
return nil, errors.New("no name set for network")
|
||||||
}
|
}
|
||||||
|
@ -854,7 +864,7 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
epConfig := &networktypes.EndpointSettings{
|
epConfig := &network.EndpointSettings{
|
||||||
GwPriority: ep.GwPriority,
|
GwPriority: ep.GwPriority,
|
||||||
}
|
}
|
||||||
epConfig.Aliases = append(epConfig.Aliases, ep.Aliases...)
|
epConfig.Aliases = append(epConfig.Aliases, ep.Aliases...)
|
||||||
|
@ -866,7 +876,7 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End
|
||||||
epConfig.Links = ep.Links
|
epConfig.Links = ep.Links
|
||||||
}
|
}
|
||||||
if ep.IPv4Address != "" || ep.IPv6Address != "" || len(ep.LinkLocalIPs) > 0 {
|
if ep.IPv4Address != "" || ep.IPv6Address != "" || len(ep.LinkLocalIPs) > 0 {
|
||||||
epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{
|
epConfig.IPAMConfig = &network.EndpointIPAMConfig{
|
||||||
IPv4Address: ep.IPv4Address,
|
IPv4Address: ep.IPv4Address,
|
||||||
IPv6Address: ep.IPv6Address,
|
IPv6Address: ep.IPv6Address,
|
||||||
LinkLocalIPs: ep.LinkLocalIPs,
|
LinkLocalIPs: ep.LinkLocalIPs,
|
||||||
|
|
|
@ -10,9 +10,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
networktypes "github.com/docker/docker/api/types/network"
|
networktypes "github.com/moby/moby/api/types/network"
|
||||||
"github.com/docker/go-connections/nat"
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
|
@ -439,7 +438,7 @@ func TestParseWithExpose(t *testing.T) {
|
||||||
"8080-NaN/tcp": `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
|
"8080-NaN/tcp": `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
|
||||||
"1234567890-8080/tcp": `invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
|
"1234567890-8080/tcp": `invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
|
||||||
}
|
}
|
||||||
valids := map[string][]nat.Port{
|
valids := map[string][]container.PortRangeProto{
|
||||||
"8080/tcp": {"8080/tcp"},
|
"8080/tcp": {"8080/tcp"},
|
||||||
"8080/udp": {"8080/udp"},
|
"8080/udp": {"8080/udp"},
|
||||||
"8080/ncp": {"8080/ncp"},
|
"8080/ncp": {"8080/ncp"},
|
||||||
|
@ -473,7 +472,7 @@ func TestParseWithExpose(t *testing.T) {
|
||||||
if len(config.ExposedPorts) != 2 {
|
if len(config.ExposedPorts) != 2 {
|
||||||
t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
|
t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
|
||||||
}
|
}
|
||||||
ports := []nat.Port{"80/tcp", "81/tcp"}
|
ports := []container.PortRangeProto{"80/tcp", "81/tcp"}
|
||||||
for _, port := range ports {
|
for _, port := range ports {
|
||||||
if _, ok := config.ExposedPorts[port]; !ok {
|
if _, ok := config.ExposedPorts[port]; !ok {
|
||||||
t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
|
t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
|
||||||
|
@ -1017,7 +1016,7 @@ func TestParseLabelfileVariables(t *testing.T) {
|
||||||
func TestParseEntryPoint(t *testing.T) {
|
func TestParseEntryPoint(t *testing.T) {
|
||||||
config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
|
config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Check(t, is.DeepEqual([]string(config.Entrypoint), []string{"anything"}))
|
assert.Check(t, is.DeepEqual(config.Entrypoint, []string{"anything"}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateDevice(t *testing.T) {
|
func TestValidateDevice(t *testing.T) {
|
||||||
|
|
|
@ -8,7 +8,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/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ type pauseOptions struct {
|
||||||
containers []string
|
containers []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPauseCommand creates a new cobra.Command for `docker pause`
|
// newPauseCommand creates a new cobra.Command for "docker container pause"
|
||||||
func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
|
func newPauseCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts pauseOptions
|
var opts pauseOptions
|
||||||
|
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
|
@ -26,14 +26,15 @@ 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
|
||||||
}),
|
}),
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"})
|
||||||
|
|
|
@ -11,8 +11,8 @@ 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/go-connections/nat"
|
|
||||||
"github.com/fvbommel/sortorder"
|
"github.com/fvbommel/sortorder"
|
||||||
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -23,8 +23,8 @@ type portOptions struct {
|
||||||
port string
|
port string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPortCommand creates a new cobra.Command for `docker port`
|
// newPortCommand creates a new cobra.Command for "docker container port".
|
||||||
func NewPortCommand(dockerCli command.Cli) *cobra.Command {
|
func newPortCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts portOptions
|
var opts portOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -36,12 +36,13 @@ func NewPortCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
opts.port = args[1]
|
opts.port = args[1]
|
||||||
}
|
}
|
||||||
return runPort(cmd.Context(), dockerCli, &opts)
|
return runPort(cmd.Context(), dockerCLI, &opts)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"aliases": "docker container port, docker port",
|
"aliases": "docker container port, docker port",
|
||||||
},
|
},
|
||||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
|
ValidArgsFunction: completion.ContainerNames(dockerCLI, false),
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -67,7 +68,7 @@ func runPort(ctx context.Context, dockerCli command.Cli, opts *portOptions) erro
|
||||||
if _, err = strconv.ParseUint(port, 10, 16); err != nil {
|
if _, err = strconv.ParseUint(port, 10, 16); err != nil {
|
||||||
return errors.Wrapf(err, "Error: invalid port (%s)", port)
|
return errors.Wrapf(err, "Error: invalid port (%s)", port)
|
||||||
}
|
}
|
||||||
frontends, exists := c.NetworkSettings.Ports[nat.Port(port+"/"+proto)]
|
frontends, exists := c.NetworkSettings.Ports[container.PortRangeProto(port+"/"+proto)]
|
||||||
if !exists || len(frontends) == 0 {
|
if !exists || len(frontends) == 0 {
|
||||||
return errors.Errorf("Error: No public port '%s' published for %s", opts.port, opts.container)
|
return errors.Errorf("Error: No public port '%s' published for %s", opts.port, opts.container)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/docker/go-connections/nat"
|
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
)
|
)
|
||||||
|
@ -47,26 +46,26 @@ func TestNewPortCommandOutput(t *testing.T) {
|
||||||
cli := test.NewFakeCli(&fakeClient{
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
inspectFunc: func(string) (container.InspectResponse, error) {
|
inspectFunc: func(string) (container.InspectResponse, error) {
|
||||||
ci := container.InspectResponse{NetworkSettings: &container.NetworkSettings{}}
|
ci := container.InspectResponse{NetworkSettings: &container.NetworkSettings{}}
|
||||||
ci.NetworkSettings.Ports = nat.PortMap{
|
ci.NetworkSettings.Ports = container.PortMap{
|
||||||
"80/tcp": make([]nat.PortBinding, len(tc.ips)),
|
"80/tcp": make([]container.PortBinding, len(tc.ips)),
|
||||||
"443/tcp": make([]nat.PortBinding, len(tc.ips)),
|
"443/tcp": make([]container.PortBinding, len(tc.ips)),
|
||||||
"443/udp": make([]nat.PortBinding, len(tc.ips)),
|
"443/udp": make([]container.PortBinding, len(tc.ips)),
|
||||||
}
|
}
|
||||||
for i, ip := range tc.ips {
|
for i, ip := range tc.ips {
|
||||||
ci.NetworkSettings.Ports["80/tcp"][i] = nat.PortBinding{
|
ci.NetworkSettings.Ports["80/tcp"][i] = container.PortBinding{
|
||||||
HostIP: ip, HostPort: "3456",
|
HostIP: ip, HostPort: "3456",
|
||||||
}
|
}
|
||||||
ci.NetworkSettings.Ports["443/tcp"][i] = nat.PortBinding{
|
ci.NetworkSettings.Ports["443/tcp"][i] = container.PortBinding{
|
||||||
HostIP: ip, HostPort: "4567",
|
HostIP: ip, HostPort: "4567",
|
||||||
}
|
}
|
||||||
ci.NetworkSettings.Ports["443/udp"][i] = nat.PortBinding{
|
ci.NetworkSettings.Ports["443/udp"][i] = container.PortBinding{
|
||||||
HostIP: ip, HostPort: "5678",
|
HostIP: ip, HostPort: "5678",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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,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/system/pruner"
|
||||||
"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"
|
||||||
|
@ -14,13 +14,20 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Register the prune command to run as part of "docker system prune"
|
||||||
|
if err := pruner.Register(pruner.TypeContainer, pruneFn); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type pruneOptions struct {
|
type pruneOptions struct {
|
||||||
force bool
|
force bool
|
||||||
filter opts.FilterOpt
|
filter opts.FilterOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
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 +35,19 @@ 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,
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
@ -85,8 +93,16 @@ type cancelledErr struct{ error }
|
||||||
|
|
||||||
func (cancelledErr) Cancelled() {}
|
func (cancelledErr) Cancelled() {}
|
||||||
|
|
||||||
// RunPrune calls the Container Prune API
|
// pruneFn calls the Container Prune API for use in "docker system prune",
|
||||||
// This returns the amount of space reclaimed and a detailed output string
|
// and returns the amount of space reclaimed and a detailed output string.
|
||||||
func RunPrune(ctx context.Context, dockerCli command.Cli, _ bool, filter opts.FilterOpt) (uint64, string, error) {
|
func pruneFn(ctx context.Context, dockerCLI command.Cli, options pruner.PruneOptions) (uint64, string, error) {
|
||||||
return runPrune(ctx, dockerCli, pruneOptions{force: true, filter: filter})
|
if !options.Confirmed {
|
||||||
|
// Dry-run: perform validation and produce confirmation before pruning.
|
||||||
|
confirmMsg := "all stopped containers"
|
||||||
|
return 0, confirmMsg, cancelledErr{errors.New("containers prune has been cancelled")}
|
||||||
|
}
|
||||||
|
return runPrune(ctx, dockerCLI, pruneOptions{
|
||||||
|
force: true,
|
||||||
|
filter: options.Filter,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/moby/moby/api/types/filters"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContainerPrunePromptTermination(t *testing.T) {
|
func TestContainerPrunePromptTermination(t *testing.T) {
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -17,8 +17,8 @@ type renameOptions struct {
|
||||||
newName string
|
newName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRenameCommand creates a new cobra.Command for `docker rename`
|
// newRenameCommand creates a new cobra.Command for "docker container rename".
|
||||||
func NewRenameCommand(dockerCli command.Cli) *cobra.Command {
|
func newRenameCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts renameOptions
|
var opts renameOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -28,12 +28,13 @@ 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),
|
||||||
|
DisableFlagsInUseLine: 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"})
|
||||||
|
|
|
@ -8,7 +8,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/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ type restartOptions struct {
|
||||||
containers []string
|
containers []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRestartCommand creates a new cobra.Command for `docker restart`
|
// newRestartCommand creates a new cobra.Command for "docker container restart".
|
||||||
func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
|
func newRestartCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts restartOptions
|
var opts restartOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -34,12 +34,13 @@ 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),
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
)
|
)
|
||||||
|
@ -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,11 +6,11 @@ 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"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ type rmOptions struct {
|
||||||
containers []string
|
containers []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRmCommand creates a new cobra.Command for `docker rm`
|
// newRmCommand creates a new cobra.Command for "docker container rm".
|
||||||
func NewRmCommand(dockerCli command.Cli) *cobra.Command {
|
func newRmCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts rmOptions
|
var opts rmOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -32,14 +32,15 @@ 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
|
||||||
}),
|
}),
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
@ -53,7 +54,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 +76,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
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"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/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/moby/sys/signal"
|
"github.com/moby/sys/signal"
|
||||||
"github.com/moby/term"
|
"github.com/moby/term"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -27,8 +27,8 @@ type runOptions struct {
|
||||||
detachKeys string
|
detachKeys string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRunCommand create a new `docker run` command
|
// newRunCommand create a new "docker run" command.
|
||||||
func NewRunCommand(dockerCli command.Cli) *cobra.Command {
|
func newRunCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var options runOptions
|
var options runOptions
|
||||||
var copts *containerOptions
|
var copts *containerOptions
|
||||||
|
|
||||||
|
@ -41,13 +41,14 @@ 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",
|
||||||
},
|
},
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
@ -66,18 +67,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", !dockerCLI.ContentTrustEnabled(), "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"
|
||||||
|
@ -16,11 +14,12 @@ import (
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
"github.com/docker/cli/internal/test/notary"
|
"github.com/docker/cli/internal/test/notary"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/moby/moby/api/pkg/progress"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/moby/moby/api/pkg/streamformatter"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/moby/moby/api/types"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/moby/moby/api/types/container"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/moby/moby/api/types/network"
|
||||||
|
"github.com/moby/moby/client"
|
||||||
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())
|
||||||
}
|
}
|
||||||
|
@ -85,14 +84,14 @@ func TestRunAttach(t *testing.T) {
|
||||||
ID: "id",
|
ID: "id",
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
containerAttachFunc: func(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
|
containerAttachFunc: func(ctx context.Context, containerID string, options container.AttachOptions) (client.HijackedResponse, error) {
|
||||||
server, client := net.Pipe()
|
server, clientConn := net.Pipe()
|
||||||
conn = server
|
conn = server
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
_ = server.Close()
|
_ = server.Close()
|
||||||
})
|
})
|
||||||
attachCh <- struct{}{}
|
attachCh <- struct{}{}
|
||||||
return types.NewHijackedResponse(client, types.MediaTypeRawStream), nil
|
return client.NewHijackedResponse(clientConn, types.MediaTypeRawStream), nil
|
||||||
},
|
},
|
||||||
waitFunc: func(_ string) (<-chan container.WaitResponse, <-chan error) {
|
waitFunc: func(_ string) (<-chan container.WaitResponse, <-chan error) {
|
||||||
responseChan := make(chan container.WaitResponse, 1)
|
responseChan := make(chan container.WaitResponse, 1)
|
||||||
|
@ -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)
|
||||||
|
@ -162,14 +161,14 @@ func TestRunAttachTermination(t *testing.T) {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
containerAttachFunc: func(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
|
containerAttachFunc: func(ctx context.Context, containerID string, options container.AttachOptions) (client.HijackedResponse, error) {
|
||||||
server, client := net.Pipe()
|
server, clientConn := net.Pipe()
|
||||||
conn = server
|
conn = server
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
_ = server.Close()
|
_ = server.Close()
|
||||||
})
|
})
|
||||||
attachCh <- struct{}{}
|
attachCh <- struct{}{}
|
||||||
return types.NewHijackedResponse(client, types.MediaTypeRawStream), nil
|
return client.NewHijackedResponse(clientConn, types.MediaTypeRawStream), nil
|
||||||
},
|
},
|
||||||
waitFunc: func(_ string) (<-chan container.WaitResponse, <-chan error) {
|
waitFunc: func(_ string) (<-chan container.WaitResponse, <-chan error) {
|
||||||
responseChan := make(chan container.WaitResponse, 1)
|
responseChan := make(chan container.WaitResponse, 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)
|
||||||
|
@ -233,44 +232,40 @@ func TestRunPullTermination(t *testing.T) {
|
||||||
) (container.CreateResponse, error) {
|
) (container.CreateResponse, error) {
|
||||||
return container.CreateResponse{}, errors.New("shouldn't try to create a container")
|
return container.CreateResponse{}, errors.New("shouldn't try to create a container")
|
||||||
},
|
},
|
||||||
containerAttachFunc: func(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
|
containerAttachFunc: func(ctx context.Context, containerID string, options container.AttachOptions) (client.HijackedResponse, error) {
|
||||||
return types.HijackedResponse{}, errors.New("shouldn't try to attach to a container")
|
return client.HijackedResponse{}, errors.New("shouldn't try to attach to a container")
|
||||||
},
|
},
|
||||||
imageCreateFunc: func(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
|
imageCreateFunc: func(ctx context.Context, parentReference string, options client.ImageCreateOptions) (io.ReadCloser, error) {
|
||||||
server, client := net.Pipe()
|
server, respReader := net.Pipe()
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
_ = 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
attachCh <- struct{}{}
|
attachCh <- struct{}{}
|
||||||
return client, nil
|
return respReader, nil
|
||||||
},
|
},
|
||||||
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"})
|
||||||
|
@ -339,7 +334,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, test.EnableContentTrust)
|
}, 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)
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
gosignal "os/signal"
|
gosignal "os/signal"
|
||||||
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/moby/moby/client"
|
||||||
"github.com/moby/sys/signal"
|
"github.com/moby/sys/signal"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue