Compare commits
246 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 912c1ddf8a | |||
| c97e8091a6 | |||
| 82bd8158f7 | |||
| 8945848025 | |||
| b54897bcb8 | |||
| cd560916f3 | |||
| 9a101a955b | |||
| 50fae20748 | |||
| 37533c2f55 | |||
| 217971d481 | |||
| fce24d5f8d | |||
| 0e4f16f3bf | |||
| 6e35a78fd9 | |||
| bf1a701820 | |||
| 7fafd33de0 | |||
| 617eb5271a | |||
| 50acbb031b | |||
| 9f8bda1df9 | |||
| 12ea47dafa | |||
| 319767c0a8 | |||
| 9e278883c5 | |||
| ad583961b6 | |||
| c8c13bdcac | |||
| 1f7561ecbb | |||
| 886661427e | |||
| 1a04810073 | |||
| b83cf582cd | |||
| 0d415ad0e9 | |||
| faf7647dcf | |||
| d8cdcaee23 | |||
| 623fcd5489 | |||
| d1cb7d41c2 | |||
| 1322f585fe | |||
| 34d42bdf0c | |||
| 334421b6b4 | |||
| 2fee28cb58 | |||
| 443617c0f1 | |||
| 2088c5963b | |||
| aebdf506bc | |||
| 64c20f3013 | |||
| a84536eb4a | |||
| 158348412d | |||
| ae21e83244 | |||
| 9b47c14430 | |||
| 3cc2d27d99 | |||
| e5d26a8d40 | |||
| 55a1f6eb73 | |||
| 4f00eee524 | |||
| 00b0eb7781 | |||
| b0793613a6 | |||
| 64206aef76 | |||
| 0bc092496f | |||
| 1f1b70dfdf | |||
| a4bfd8c744 | |||
| 468a76779d | |||
| f52570645c | |||
| 32031fb5ab | |||
| eae63069e6 | |||
| 3d017f30d6 | |||
| 20cba0a2ee | |||
| e0972e94b8 | |||
| 43210216ff | |||
| 70b53a0c15 | |||
| c07cee05e2 | |||
| 860a139930 | |||
| 1fd8e2434b | |||
| 69041853a8 | |||
| c8f5d4cc6d | |||
| e06bee6999 | |||
| 3b0d297aab | |||
| bc291f0c98 | |||
| 11a3d8728b | |||
| d69d501f69 | |||
| 1bfec63b9f | |||
| 8376b3e428 | |||
| 925e7d6870 | |||
| 4ea91a73ee | |||
| 91b63ac450 | |||
| a2ccd3eb00 | |||
| 634b19f5ed | |||
| 8bcd7f3ee1 | |||
| 872935f1c0 | |||
| 9dabf16f76 | |||
| 504162642f | |||
| 1fc73b470b | |||
| 540b28e7bb | |||
| 1671ee8b49 | |||
| e9c0fc330c | |||
| 70118aebef | |||
| 3dcc653533 | |||
| 7fbadef49b | |||
| 8c6e43fd07 | |||
| b23bc8cacd | |||
| f80d7182ca | |||
| ad62b68693 | |||
| ed2d365653 | |||
| 6e7266a72a | |||
| 6b93cf221a | |||
| 52eddcf4e4 | |||
| b79d684653 | |||
| e06ef800fc | |||
| 32ac7a08f8 | |||
| 966fa7c475 | |||
| 892784deed | |||
| 0022fe7111 | |||
| 3ce3177477 | |||
| 591bd17424 | |||
| 43b97e8880 | |||
| df474d5176 | |||
| 0ba14fde41 | |||
| 7db922cf9f | |||
| dfec976e84 | |||
| e93abde7a0 | |||
| cba002eb5e | |||
| 43b840ed93 | |||
| c481c64922 | |||
| 851277f966 | |||
| 1a83595c7c | |||
| 0d4de2392b | |||
| 6b58179017 | |||
| 1f5999f6cb | |||
| d11fbda360 | |||
| 28836375b3 | |||
| d0057db3ac | |||
| 3f0d90a2a9 | |||
| 7ae9f2738c | |||
| 97b7746df0 | |||
| 482bf8613c | |||
| a5058b82c2 | |||
| 94f9de5928 | |||
| a731722652 | |||
| 0502189e28 | |||
| 9683d06337 | |||
| db2672e685 | |||
| 0ed8a7e310 | |||
| 9b61bbb652 | |||
| 465208e056 | |||
| 9acfbbd74f | |||
| bc2e274782 | |||
| 6b5ebfb4f7 | |||
| 23148220ec | |||
| de4ab4bd06 | |||
| a9ea034815 | |||
| d5dd249469 | |||
| 630e1d3e95 | |||
| f82007d3ca | |||
| 558a910b85 | |||
| be2c284ee2 | |||
| 8b924a5119 | |||
| 20d1b661bc | |||
| ce85b24440 | |||
| 7edcaca761 | |||
| b6a3ce4167 | |||
| d4491fc093 | |||
| 05c7d4b319 | |||
| b9cd722595 | |||
| 3ba4d27805 | |||
| 1a8fa8b73e | |||
| 2ef6bc1c1b | |||
| e2fc6bd771 | |||
| 16c8f4942e | |||
| b7548ba7f1 | |||
| cd4d62e979 | |||
| 12aaeae21b | |||
| f53a2ae443 | |||
| fed9fa0f72 | |||
| 3da25f6c6d | |||
| 40a1da8b42 | |||
| 2e9eff235d | |||
| 54291dd47a | |||
| 8ed44f916f | |||
| 4d28ae98f4 | |||
| 9d9bb19f01 | |||
| 358d499681 | |||
| 7b4171c608 | |||
| 90bd9c5308 | |||
| 10b4bb073f | |||
| 7dc271a8be | |||
| 3cf12ae719 | |||
| d5d94e46fc | |||
| 7f9dba60e2 | |||
| b7583a2c28 | |||
| 068f118f88 | |||
| 82ed39e319 | |||
| e70f68595d | |||
| 0b7aff333b | |||
| f0e7e07b8b | |||
| 05905bd922 | |||
| 421e3b5e91 | |||
| dc75e9ed6c | |||
| c80adf4e87 | |||
| de91207b87 | |||
| 426fb2fd81 | |||
| 57a1180c52 | |||
| 997cfa699b | |||
| 6d21372dbb | |||
| 49c0e1996a | |||
| 803b980e9d | |||
| 296a6f5872 | |||
| c5dd1d0951 | |||
| cbe9469364 | |||
| 1527d625f3 | |||
| 3fdf377b66 | |||
| 6a4d38c7f2 | |||
| 4445e77025 | |||
| ad3bcf8f3d | |||
| 28c5652a09 | |||
| ffd57fac12 | |||
| 0937c8b926 | |||
| dbf8443668 | |||
| db8b8099b4 | |||
| 5f4f4f64d3 | |||
| f07834d185 | |||
| 61fe22f21a | |||
| 02537eac59 | |||
| 0ad1d55b02 | |||
| 4758ed0b0d | |||
| e8bfedd266 | |||
| 6c70360c79 | |||
| 4f1403bd0f | |||
| eb99994c75 | |||
| 0bdc20ecbe | |||
| 8d6e571c03 | |||
| 31644d5ea7 | |||
| b7e25a4b48 | |||
| 647ccf3433 | |||
| 2c8a5f7475 | |||
| e3216ca64d | |||
| 692c7ee7e8 | |||
| e1dcc194e3 | |||
| e81b8355b6 | |||
| 67aa271410 | |||
| 57386570a1 | |||
| 1d666b4105 | |||
| 5ccb48459b | |||
| 7f15dfa4d5 | |||
| 86162f7816 | |||
| 865190615b | |||
| e73fde8ca2 | |||
| dccfd6e4d0 | |||
| 116db4fc82 | |||
| 73959eef71 | |||
| 1433df8fee | |||
| 0c2697d779 | |||
| 094af6ea07 | |||
| faf096b25c |
7
.github/CODEOWNERS
vendored
7
.github/CODEOWNERS
vendored
@ -1,7 +1,6 @@
|
||||
# GitHub code owners
|
||||
# See https://github.com/blog/2392-introducing-code-owners
|
||||
|
||||
cli/command/stack/** @silvin-lubecki
|
||||
contrib/completion/bash/** @albers
|
||||
contrib/completion/zsh/** @sdurrheimer
|
||||
docs/** @thaJeztah
|
||||
cli/command/stack/** @silvin-lubecki @docker/runtime-owners
|
||||
contrib/completion/bash/** @albers @docker/runtime-owners
|
||||
docs/** @thaJeztah @docker/runtime-owners
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -8,12 +8,12 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to report a bug!
|
||||
If this is a security issue please report it to the [Docker Security team](mailto:security@docker.com).
|
||||
If this is a security issue report it to the [Docker Security team](mailto:security@docker.com).
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Please give a clear and concise description of the bug
|
||||
description: Give a clear and concise description of the bug
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -4,7 +4,7 @@ contact_links:
|
||||
about: "Read guidelines and tips about contributing to Docker."
|
||||
url: "https://github.com/docker/cli/blob/master/CONTRIBUTING.md"
|
||||
- name: "Security and Vulnerabilities"
|
||||
about: "Please report any security issues or vulnerabilities responsibly to the Docker security team. Please do not use the public issue tracker."
|
||||
about: "Report any security issues or vulnerabilities responsibly to the Docker security team. Do not use the public issue tracker."
|
||||
url: "https://github.com/moby/moby/security/policy"
|
||||
- name: "General Support"
|
||||
about: "Get the help you need to build, share, and run your Docker applications"
|
||||
|
||||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Please make sure you've read and understood our contributing guidelines;
|
||||
Make sure you've read and understood our contributing guidelines;
|
||||
https://github.com/docker/cli/blob/master/CONTRIBUTING.md
|
||||
|
||||
** Make sure all your commits include a signature generated with `git commit -s` **
|
||||
@ -10,7 +10,7 @@ guide https://docs.docker.com/opensource/code/
|
||||
If this is a bug fix, make sure your description includes "fixes #xxxx", or
|
||||
"closes #xxxx"
|
||||
|
||||
Please provide the following information:
|
||||
Provide the following information:
|
||||
-->
|
||||
|
||||
**- What I did**
|
||||
|
||||
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@ -19,7 +19,7 @@ on:
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
matrix: ${{ steps.platforms.outputs.matrix }}
|
||||
steps:
|
||||
@ -37,7 +37,7 @@ jobs:
|
||||
echo ${{ steps.platforms.outputs.matrix }}
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- prepare
|
||||
strategy:
|
||||
@ -61,7 +61,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v5
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
set: |
|
||||
@ -90,7 +90,7 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
bin-image:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name != 'pull_request' && github.repository == 'docker/cli' }}
|
||||
steps:
|
||||
-
|
||||
@ -122,7 +122,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_CLIBIN_TOKEN }}
|
||||
-
|
||||
name: Build and push image
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v5
|
||||
with:
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
@ -134,7 +134,7 @@ jobs:
|
||||
*.cache-to=type=gha,scope=bin-image,mode=max
|
||||
|
||||
prepare-plugins:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
matrix: ${{ steps.platforms.outputs.matrix }}
|
||||
steps:
|
||||
@ -152,7 +152,7 @@ jobs:
|
||||
echo ${{ steps.platforms.outputs.matrix }}
|
||||
|
||||
plugins:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- prepare-plugins
|
||||
strategy:
|
||||
@ -168,7 +168,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v5
|
||||
with:
|
||||
targets: plugins-cross
|
||||
set: |
|
||||
|
||||
20
.github/workflows/codeql.yml
vendored
20
.github/workflows/codeql.yml
vendored
@ -44,16 +44,6 @@ jobs:
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
git checkout HEAD^2
|
||||
-
|
||||
name: Update Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: go
|
||||
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
|
||||
# and is trying to be smart by searching for modules in every directory,
|
||||
# including vendor directories. If no module is found, it's creating one
|
||||
@ -64,6 +54,16 @@ jobs:
|
||||
run: |
|
||||
ln -s vendor.mod go.mod
|
||||
ln -s vendor.sum go.sum
|
||||
-
|
||||
name: Update Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: go
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
17
.github/workflows/e2e.yml
vendored
17
.github/workflows/e2e.yml
vendored
@ -16,7 +16,7 @@ on:
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@ -28,8 +28,8 @@ jobs:
|
||||
- alpine
|
||||
- debian
|
||||
engine-version:
|
||||
- 25.0 # latest
|
||||
- 24.0 # latest - 1
|
||||
- 27.0 # latest
|
||||
- 26.1 # latest - 1
|
||||
- 23.0 # mirantis lts
|
||||
# TODO(krissetto) 19.03 needs a look, doesn't work ubuntu 22.04 (cgroup errors).
|
||||
# we could have a separate job that tests it against ubuntu 20.04
|
||||
@ -40,8 +40,15 @@ jobs:
|
||||
-
|
||||
name: Update daemon.json
|
||||
run: |
|
||||
sudo jq '.experimental = true' < /etc/docker/daemon.json > /tmp/docker.json
|
||||
sudo mv /tmp/docker.json /etc/docker/daemon.json
|
||||
if [ ! -f /etc/docker/daemon.json ]; then
|
||||
# ubuntu 24.04 runners no longer have a default daemon.json present
|
||||
sudo mkdir -p /etc/docker/
|
||||
echo '{"experimental": true}' | sudo tee /etc/docker/daemon.json
|
||||
else
|
||||
# but if there is one; let's patch it to keep other options that may be set.
|
||||
sudo jq '.experimental = true' < /etc/docker/daemon.json > /tmp/docker.json
|
||||
sudo mv /tmp/docker.json /etc/docker/daemon.json
|
||||
fi
|
||||
sudo cat /etc/docker/daemon.json
|
||||
sudo service docker restart
|
||||
docker version
|
||||
|
||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@ -16,7 +16,7 @@ on:
|
||||
|
||||
jobs:
|
||||
ctn:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
@ -26,7 +26,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v5
|
||||
with:
|
||||
targets: test-coverage
|
||||
-
|
||||
@ -64,7 +64,7 @@ jobs:
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.21.12
|
||||
go-version: 1.21.11
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
|
||||
2
.github/workflows/validate-pr.yml
vendored
2
.github/workflows/validate-pr.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
||||
desc=$(echo "$block" | awk NF)
|
||||
|
||||
if [ -z "$desc" ]; then
|
||||
echo "::error::Changelog section is empty. Please provide a description for the changelog."
|
||||
echo "::error::Changelog section is empty. Provide a description for the changelog."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
8
.github/workflows/validate.yml
vendored
8
.github/workflows/validate.yml
vendored
@ -16,7 +16,7 @@ on:
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@ -31,13 +31,13 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Run
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v5
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
# check that the generated Markdown and the checked-in files match
|
||||
validate-md:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
@ -57,7 +57,7 @@ jobs:
|
||||
fi
|
||||
|
||||
validate-make:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,5 @@
|
||||
# if you want to ignore files created by your editor/tools,
|
||||
# please consider a global .gitignore https://help.github.com/articles/ignoring-files
|
||||
# consider a global .gitignore https://help.github.com/articles/ignoring-files
|
||||
*.exe
|
||||
*.exe~
|
||||
*.orig
|
||||
|
||||
@ -44,9 +44,6 @@ linters:
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
skip-files:
|
||||
- cli/compose/schema/bindata.go
|
||||
- .*generated.*
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
@ -58,7 +55,8 @@ linters-settings:
|
||||
gocyclo:
|
||||
min-complexity: 16
|
||||
govet:
|
||||
check-shadowing: true
|
||||
enable:
|
||||
- shadow
|
||||
settings:
|
||||
shadow:
|
||||
strict: true
|
||||
@ -94,6 +92,10 @@ issues:
|
||||
exclude:
|
||||
- parameter .* always receives
|
||||
|
||||
exclude-files:
|
||||
- cli/compose/schema/bindata.go
|
||||
- .*generated.*
|
||||
|
||||
exclude-rules:
|
||||
# We prefer to use an "exclude-list" so that new "default" exclusions are not
|
||||
# automatically inherited. We can decide whether or not to follow upstream
|
||||
|
||||
10
.mailmap
10
.mailmap
@ -22,7 +22,10 @@ Akihiro Matsushima <amatsusbit@gmail.com> <amatsus@users.noreply.github.com>
|
||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
|
||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> <suda.akihiro@lab.ntt.co.jp>
|
||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> <suda.kyoto@gmail.com>
|
||||
Alano Terblanche <alano.terblanche@docker.com>
|
||||
Alano Terblanche <alano.terblanche@docker.com> <18033717+Benehiko@users.noreply.github.com>
|
||||
Albin Kerouanton <albinker@gmail.com>
|
||||
Albin Kerouanton <albinker@gmail.com> <557933+akerouanton@users.noreply.github.com>
|
||||
Albin Kerouanton <albinker@gmail.com> <albin@akerouanton.name>
|
||||
Aleksa Sarai <asarai@suse.de>
|
||||
Aleksa Sarai <asarai@suse.de> <asarai@suse.com>
|
||||
@ -88,6 +91,7 @@ Brian Goff <cpuguy83@gmail.com>
|
||||
Brian Goff <cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.home>
|
||||
Brian Goff <cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.local>
|
||||
Brian Tracy <brian.tracy33@gmail.com>
|
||||
Calvin Liu <flycalvin@qq.com>
|
||||
Carlos de Paula <me@carlosedp.com>
|
||||
Chad Faragher <wyckster@hotmail.com>
|
||||
Chander Govindarajan <chandergovind@gmail.com>
|
||||
@ -254,6 +258,7 @@ Jessica Frazelle <jess@oxide.computer> <jessfraz@google.com>
|
||||
Jessica Frazelle <jess@oxide.computer> <jfrazelle@users.noreply.github.com>
|
||||
Jessica Frazelle <jess@oxide.computer> <me@jessfraz.com>
|
||||
Jessica Frazelle <jess@oxide.computer> <princess@docker.com>
|
||||
Jim Chen <njucjc@gmail.com>
|
||||
Jim Galasyn <jim.galasyn@docker.com>
|
||||
Jiuyue Ma <majiuyue@huawei.com>
|
||||
Joey Geiger <jgeiger@gmail.com>
|
||||
@ -269,6 +274,8 @@ John Howard <github@lowenna.com> <jhowardmsft@users.noreply.github.com>
|
||||
John Howard <github@lowenna.com> <John.Howard@microsoft.com>
|
||||
John Howard <github@lowenna.com> <john.howard@microsoft.com>
|
||||
John Stephens <johnstep@docker.com> <johnstep@users.noreply.github.com>
|
||||
Jonathan A. Sternberg <jonathansternberg@gmail.com>
|
||||
Jonathan A. Sternberg <jonathansternberg@gmail.com> <jonathan.sternberg@docker.com>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
Jordan Jennings <jjn2009@gmail.com> <jjn2009@users.noreply.github.com>
|
||||
Jorit Kleine-Möllhoff <joppich@bricknet.de> <joppich@users.noreply.github.com>
|
||||
@ -444,6 +451,7 @@ Roch Feuillade <roch.feuillade@pandobac.com>
|
||||
Roch Feuillade <roch.feuillade@pandobac.com> <46478807+rochfeu@users.noreply.github.com>
|
||||
Roman Dudin <katrmr@gmail.com> <decadent@users.noreply.github.com>
|
||||
Ross Boucher <rboucher@gmail.com>
|
||||
Rui JingAn <quiterace@gmail.com>
|
||||
Runshen Zhu <runshen.zhu@gmail.com>
|
||||
Ryan Stelly <ryan.stelly@live.com>
|
||||
Sakeven Jiang <jc5930@sina.cn>
|
||||
@ -524,6 +532,8 @@ Tim Bart <tim@fewagainstmany.com>
|
||||
Tim Bosse <taim@bosboot.org> <maztaim@users.noreply.github.com>
|
||||
Tim Ruffles <oi@truffles.me.uk> <timruffles@googlemail.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Tim Welsh <timothy.welsh@docker.com>
|
||||
Tim Welsh <timothy.welsh@docker.com> <84401379+twelsh-aw@users.noreply.github.com>
|
||||
Tim Zju <21651152@zju.edu.cn>
|
||||
Timothy Hobbs <timothyhobbs@seznam.cz>
|
||||
Toli Kuznets <toli@docker.com>
|
||||
|
||||
20
AUTHORS
20
AUTHORS
@ -26,6 +26,7 @@ Akhil Mohan <akhil.mohan@mayadata.io>
|
||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
|
||||
Akim Demaille <akim.demaille@docker.com>
|
||||
Alan Thompson <cloojure@gmail.com>
|
||||
Alano Terblanche <alano.terblanche@docker.com>
|
||||
Albert Callarisa <shark234@gmail.com>
|
||||
Alberto Roura <mail@albertoroura.com>
|
||||
Albin Kerouanton <albinker@gmail.com>
|
||||
@ -65,6 +66,7 @@ Andrew Hsu <andrewhsu@docker.com>
|
||||
Andrew Macpherson <hopscotch23@gmail.com>
|
||||
Andrew McDonnell <bugs@andrewmcdonnell.net>
|
||||
Andrew Po <absourd.noise@gmail.com>
|
||||
Andrew-Zipperer <atzipperer@gmail.com>
|
||||
Andrey Petrov <andrey.petrov@shazow.net>
|
||||
Andrii Berehuliak <berkusandrew@gmail.com>
|
||||
André Martins <aanm90@gmail.com>
|
||||
@ -124,11 +126,13 @@ Bryan Bess <squarejaw@bsbess.com>
|
||||
Bryan Boreham <bjboreham@gmail.com>
|
||||
Bryan Murphy <bmurphy1976@gmail.com>
|
||||
bryfry <bryon.fryer@gmail.com>
|
||||
Calvin Liu <flycalvin@qq.com>
|
||||
Cameron Spear <cameronspear@gmail.com>
|
||||
Cao Weiwei <cao.weiwei30@zte.com.cn>
|
||||
Carlo Mion <mion00@gmail.com>
|
||||
Carlos Alexandro Becker <caarlos0@gmail.com>
|
||||
Carlos de Paula <me@carlosedp.com>
|
||||
Casey Korver <casey@korver.dev>
|
||||
Ce Gao <ce.gao@outlook.com>
|
||||
Cedric Davies <cedricda@microsoft.com>
|
||||
Cezar Sa Espinola <cezarsa@gmail.com>
|
||||
@ -160,6 +164,8 @@ Christophe Vidal <kriss@krizalys.com>
|
||||
Christopher Biscardi <biscarch@sketcht.com>
|
||||
Christopher Crone <christopher.crone@docker.com>
|
||||
Christopher Jones <tophj@linux.vnet.ibm.com>
|
||||
Christopher Petito <47751006+krissetto@users.noreply.github.com>
|
||||
Christopher Petito <chrisjpetito@gmail.com>
|
||||
Christopher Svensson <stoffus@stoffus.com>
|
||||
Christy Norman <christy@linux.vnet.ibm.com>
|
||||
Chun Chen <ramichen@tencent.com>
|
||||
@ -212,6 +218,7 @@ David Cramer <davcrame@cisco.com>
|
||||
David Dooling <dooling@gmail.com>
|
||||
David Gageot <david@gageot.net>
|
||||
David Karlsson <david.karlsson@docker.com>
|
||||
David le Blanc <systemmonkey42@users.noreply.github.com>
|
||||
David Lechner <david@lechnology.com>
|
||||
David Scott <dave@recoil.org>
|
||||
David Sheets <dsheets@docker.com>
|
||||
@ -298,6 +305,7 @@ Gang Qiao <qiaohai8866@gmail.com>
|
||||
Gary Schaetz <gary@schaetzkc.com>
|
||||
Genki Takiuchi <genki@s21g.com>
|
||||
George MacRorie <gmacr31@gmail.com>
|
||||
George Margaritis <gmargaritis@protonmail.com>
|
||||
George Xie <georgexsh@gmail.com>
|
||||
Gianluca Borello <g.borello@gmail.com>
|
||||
Gildas Cuisinier <gildas.cuisinier@gcuisinier.net>
|
||||
@ -306,6 +314,7 @@ Gleb Stsenov <gleb.stsenov@gmail.com>
|
||||
Goksu Toprak <goksu.toprak@docker.com>
|
||||
Gou Rao <gou@portworx.com>
|
||||
Govind Rai <raigovind93@gmail.com>
|
||||
Grace Choi <grace.54109@gmail.com>
|
||||
Graeme Wiebe <graeme.wiebe@gmail.com>
|
||||
Grant Reaber <grant.reaber@gmail.com>
|
||||
Greg Pflaum <gpflaum@users.noreply.github.com>
|
||||
@ -386,6 +395,7 @@ Jezeniel Zapanta <jpzapanta22@gmail.com>
|
||||
Jian Zhang <zhangjian.fnst@cn.fujitsu.com>
|
||||
Jie Luo <luo612@zju.edu.cn>
|
||||
Jilles Oldenbeuving <ojilles@gmail.com>
|
||||
Jim Chen <njucjc@gmail.com>
|
||||
Jim Galasyn <jim.galasyn@docker.com>
|
||||
Jim Lin <b04705003@ntu.edu.tw>
|
||||
Jimmy Leger <jimmy.leger@gmail.com>
|
||||
@ -416,6 +426,7 @@ John Willis <john.willis@docker.com>
|
||||
Jon Johnson <jonjohnson@google.com>
|
||||
Jon Zeolla <zeolla@gmail.com>
|
||||
Jonatas Baldin <jonatas.baldin@gmail.com>
|
||||
Jonathan A. Sternberg <jonathansternberg@gmail.com>
|
||||
Jonathan Boulle <jonathanboulle@gmail.com>
|
||||
Jonathan Lee <jonjohn1232009@gmail.com>
|
||||
Jonathan Lomas <jonathan@floatinglomas.ca>
|
||||
@ -470,6 +481,7 @@ Kevin Woblick <mail@kovah.de>
|
||||
khaled souf <khaled.souf@gmail.com>
|
||||
Kim Eik <kim@heldig.org>
|
||||
Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
Kirill A. Korinsky <kirill@korins.ky>
|
||||
Kotaro Yoshimatsu <kotaro.yoshimatsu@gmail.com>
|
||||
Krasi Georgiev <krasi@vip-consult.solutions>
|
||||
Kris-Mikael Krister <krismikael@protonmail.com>
|
||||
@ -530,6 +542,7 @@ Marco Vedovati <mvedovati@suse.com>
|
||||
Marcus Martins <marcus@docker.com>
|
||||
Marianna Tessel <mtesselh@gmail.com>
|
||||
Marius Ileana <marius.ileana@gmail.com>
|
||||
Marius Meschter <marius@meschter.me>
|
||||
Marius Sturm <marius@graylog.com>
|
||||
Mark Oates <fl0yd@me.com>
|
||||
Marsh Macy <marsma@microsoft.com>
|
||||
@ -538,6 +551,7 @@ Mary Anthony <mary.anthony@docker.com>
|
||||
Mason Fish <mason.fish@docker.com>
|
||||
Mason Malone <mason.malone@gmail.com>
|
||||
Mateusz Major <apkd@users.noreply.github.com>
|
||||
Mathias Duedahl <64321057+Lussebullen@users.noreply.github.com>
|
||||
Mathieu Champlon <mathieu.champlon@docker.com>
|
||||
Mathieu Rollet <matletix@gmail.com>
|
||||
Matt Gucci <matt9ucci@gmail.com>
|
||||
@ -547,6 +561,7 @@ Matthew Heon <mheon@redhat.com>
|
||||
Matthieu Hauglustaine <matt.hauglustaine@gmail.com>
|
||||
Mauro Porras P <mauroporrasp@gmail.com>
|
||||
Max Shytikov <mshytikov@gmail.com>
|
||||
Max-Julian Pogner <max-julian@pogner.at>
|
||||
Maxime Petazzoni <max@signalfuse.com>
|
||||
Maximillian Fan Xavier <maximillianfx@gmail.com>
|
||||
Mei ChunTao <mei.chuntao@zte.com.cn>
|
||||
@ -610,6 +625,7 @@ Nathan McCauley <nathan.mccauley@docker.com>
|
||||
Neil Peterson <neilpeterson@outlook.com>
|
||||
Nick Adcock <nick.adcock@docker.com>
|
||||
Nick Santos <nick.santos@docker.com>
|
||||
Nick Sieger <nick@nicksieger.com>
|
||||
Nico Stapelbroek <nstapelbroek@gmail.com>
|
||||
Nicola Kabar <nicolaka@gmail.com>
|
||||
Nicolas Borboën <ponsfrilus@gmail.com>
|
||||
@ -704,6 +720,7 @@ Rory Hunter <roryhunter2@gmail.com>
|
||||
Ross Boucher <rboucher@gmail.com>
|
||||
Rubens Figueiredo <r.figueiredo.52@gmail.com>
|
||||
Rui Cao <ruicao@alauda.io>
|
||||
Rui JingAn <quiterace@gmail.com>
|
||||
Ryan Belgrave <rmb1993@gmail.com>
|
||||
Ryan Detzel <ryan.detzel@gmail.com>
|
||||
Ryan Stelly <ryan.stelly@live.com>
|
||||
@ -797,6 +814,7 @@ Tim Hockin <thockin@google.com>
|
||||
Tim Sampson <tim@sampson.fi>
|
||||
Tim Smith <timbot@google.com>
|
||||
Tim Waugh <twaugh@redhat.com>
|
||||
Tim Welsh <timothy.welsh@docker.com>
|
||||
Tim Wraight <tim.wraight@tangentlabs.co.uk>
|
||||
timfeirg <kkcocogogo@gmail.com>
|
||||
Timothy Hobbs <timothyhobbs@seznam.cz>
|
||||
@ -880,9 +898,11 @@ Zhang Wei <zhangwei555@huawei.com>
|
||||
Zhang Wentao <zhangwentao234@huawei.com>
|
||||
ZhangHang <stevezhang2014@gmail.com>
|
||||
zhenghenghuo <zhenghenghuo@zju.edu.cn>
|
||||
Zhiwei Liang <zliang@akamai.com>
|
||||
Zhou Hao <zhouhao@cn.fujitsu.com>
|
||||
Zhoulin Xie <zhoulin.xie@daocloud.io>
|
||||
Zhu Guihua <zhugh.fnst@cn.fujitsu.com>
|
||||
Zhuo Zhi <h.dwwwwww@gmail.com>
|
||||
Álex González <agonzalezro@gmail.com>
|
||||
Álvaro Lázaro <alvaro.lazaro.g@gmail.com>
|
||||
Átila Camurça Alves <camurca.home@gmail.com>
|
||||
|
||||
@ -16,9 +16,9 @@ start participating.
|
||||
## Reporting security issues
|
||||
|
||||
The Docker maintainers take security seriously. If you discover a security
|
||||
issue, please bring it to their attention right away!
|
||||
issue, bring it to their attention right away!
|
||||
|
||||
Please **DO NOT** file a public issue, instead send your report privately to
|
||||
**DO NOT** file a public issue, instead send your report privately to
|
||||
[security@docker.com](mailto:security@docker.com).
|
||||
|
||||
Security reports are greatly appreciated and we will publicly thank you for it.
|
||||
@ -39,7 +39,7 @@ If you find a match, you can use the "subscribe" button to get notified on
|
||||
updates. Do *not* leave random "+1" or "I have this too" comments, as they
|
||||
only clutter the discussion, and don't help resolving it. However, if you
|
||||
have ways to reproduce the issue or have additional information that may help
|
||||
resolving the issue, please leave a comment.
|
||||
resolving the issue, leave a comment.
|
||||
|
||||
When reporting issues, always include:
|
||||
|
||||
@ -166,10 +166,10 @@ Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in the pull requ
|
||||
description that close an issue. Including references automatically closes the issue
|
||||
on a merge.
|
||||
|
||||
Please do not add yourself to the `AUTHORS` file, as it is regenerated regularly
|
||||
Do not add yourself to the `AUTHORS` file, as it is regenerated regularly
|
||||
from the Git history.
|
||||
|
||||
Please see the [Coding Style](#coding-style) for further guidelines.
|
||||
See the [Coding Style](#coding-style) for further guidelines.
|
||||
|
||||
### Merge approval
|
||||
|
||||
@ -269,8 +269,8 @@ guidelines for the community as a whole:
|
||||
|
||||
* Stay on topic: Make sure that you are posting to the correct channel and
|
||||
avoid off-topic discussions. Remember when you update an issue or respond
|
||||
to an email you are potentially sending to a large number of people. Please
|
||||
consider this before you update. Also remember that nobody likes spam.
|
||||
to an email you are potentially sending to a large number of people. Consider
|
||||
this before you update. Also remember that nobody likes spam.
|
||||
|
||||
* Don't send email to the maintainers: There's no need to send email to the
|
||||
maintainers to ask them to investigate an issue or to take a look at a
|
||||
|
||||
@ -4,12 +4,12 @@ ARG BASE_VARIANT=alpine
|
||||
ARG ALPINE_VERSION=3.20
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
|
||||
ARG GO_VERSION=1.21.12
|
||||
ARG GO_VERSION=1.21.11
|
||||
ARG XX_VERSION=1.4.0
|
||||
ARG GOVERSIONINFO_VERSION=v1.3.0
|
||||
ARG GOTESTSUM_VERSION=v1.10.0
|
||||
ARG BUILDX_VERSION=0.12.1
|
||||
ARG COMPOSE_VERSION=v2.24.3
|
||||
ARG BUILDX_VERSION=0.15.1
|
||||
ARG COMPOSE_VERSION=v2.28.0
|
||||
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
@ -63,7 +63,6 @@ ARG PACKAGER_NAME
|
||||
COPY --link --from=goversioninfo /out/goversioninfo /usr/bin/goversioninfo
|
||||
RUN --mount=type=bind,target=.,ro \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=from=dockercore/golang-cross:xx-sdk-extras,target=/xx-sdk,src=/xx-sdk \
|
||||
--mount=type=tmpfs,target=cli/winresources \
|
||||
# override the default behavior of go with xx-go
|
||||
xx-go --wrap && \
|
||||
@ -89,7 +88,6 @@ ARG GO_STRIP
|
||||
ARG CGO_ENABLED
|
||||
ARG VERSION
|
||||
RUN --mount=ro --mount=type=cache,target=/root/.cache \
|
||||
--mount=from=dockercore/golang-cross:xx-sdk-extras,target=/xx-sdk,src=/xx-sdk \
|
||||
xx-go --wrap && \
|
||||
TARGET=/out ./scripts/build/plugins e2e/cli-plugins/plugins/*
|
||||
|
||||
|
||||
2
NOTICE
2
NOTICE
@ -14,6 +14,6 @@ United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
|
||||
For more information, please see https://www.bis.doc.gov
|
||||
For more information, see https://www.bis.doc.gov
|
||||
|
||||
See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
||||
|
||||
@ -67,7 +67,7 @@ make -f docker.Makefile shell
|
||||
## Legal
|
||||
|
||||
*Brought to you courtesy of our legal counsel. For more context,
|
||||
please see the [NOTICE](https://github.com/docker/cli/blob/master/NOTICE) document in this repo.*
|
||||
see the [NOTICE](https://github.com/docker/cli/blob/master/NOTICE) document in this repo.*
|
||||
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
@ -75,7 +75,7 @@ United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
|
||||
For more information, please see https://www.bis.doc.gov
|
||||
For more information, see https://www.bis.doc.gov
|
||||
|
||||
## Licensing
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package manager
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ package manager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
@ -13,7 +13,7 @@ func TestPluginError(t *testing.T) {
|
||||
err := NewPluginError("new error")
|
||||
assert.Check(t, is.Error(err, "new error"))
|
||||
|
||||
inner := fmt.Errorf("testing")
|
||||
inner := errors.New("testing")
|
||||
err = wrapAsPluginError(inner, "wrapping")
|
||||
assert.Check(t, is.Error(err, "wrapping: testing"))
|
||||
assert.Check(t, is.ErrorIs(err, inner))
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
@ -28,29 +29,36 @@ type HookPluginData struct {
|
||||
// a main CLI command was executed. It calls the hook subcommand for all
|
||||
// present CLI plugins that declare support for hooks in their metadata and
|
||||
// parses/prints their responses.
|
||||
func RunCLICommandHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, cmdErrorMessage string) {
|
||||
func RunCLICommandHooks(ctx context.Context, dockerCli command.Cli, rootCmd, subCommand *cobra.Command, cmdErrorMessage string) {
|
||||
commandName := strings.TrimPrefix(subCommand.CommandPath(), rootCmd.Name()+" ")
|
||||
flags := getCommandFlags(subCommand)
|
||||
|
||||
runHooks(dockerCli, rootCmd, subCommand, commandName, flags, cmdErrorMessage)
|
||||
runHooks(ctx, dockerCli, rootCmd, subCommand, commandName, flags, cmdErrorMessage)
|
||||
}
|
||||
|
||||
// RunPluginHooks is the entrypoint for the hooks execution flow
|
||||
// after a plugin command was just executed by the CLI.
|
||||
func RunPluginHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, args []string) {
|
||||
func RunPluginHooks(ctx context.Context, dockerCli command.Cli, rootCmd, subCommand *cobra.Command, args []string) {
|
||||
commandName := strings.Join(args, " ")
|
||||
flags := getNaiveFlags(args)
|
||||
|
||||
runHooks(dockerCli, rootCmd, subCommand, commandName, flags, "")
|
||||
runHooks(ctx, dockerCli, rootCmd, subCommand, commandName, flags, "")
|
||||
}
|
||||
|
||||
func runHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) {
|
||||
nextSteps := invokeAndCollectHooks(dockerCli, rootCmd, subCommand, invokedCommand, flags, cmdErrorMessage)
|
||||
func runHooks(ctx context.Context, dockerCli command.Cli, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string, cmdErrorMessage string) {
|
||||
nextSteps := invokeAndCollectHooks(ctx, dockerCli, rootCmd, subCommand, invokedCommand, flags, cmdErrorMessage)
|
||||
|
||||
hooks.PrintNextSteps(dockerCli.Err(), nextSteps)
|
||||
}
|
||||
|
||||
func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []string {
|
||||
func invokeAndCollectHooks(ctx context.Context, dockerCli command.Cli, rootCmd, subCmd *cobra.Command, subCmdStr string, flags map[string]string, cmdErrorMessage string) []string {
|
||||
// check if the context was cancelled before invoking hooks
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
|
||||
pluginsCfg := dockerCli.ConfigFile().Plugins
|
||||
if pluginsCfg == nil {
|
||||
return nil
|
||||
@ -68,7 +76,7 @@ func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command
|
||||
continue
|
||||
}
|
||||
|
||||
hookReturn, err := p.RunHook(HookPluginData{
|
||||
hookReturn, err := p.RunHook(ctx, HookPluginData{
|
||||
RootCmd: match,
|
||||
Flags: flags,
|
||||
CommandError: cmdErrorMessage,
|
||||
|
||||
@ -49,6 +49,16 @@ func IsNotFound(err error) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// getPluginDirs returns the platform-specific locations to search for plugins
|
||||
// in order of preference.
|
||||
//
|
||||
// Plugin-discovery is performed in the following order of preference:
|
||||
//
|
||||
// 1. The "cli-plugins" directory inside the CLIs [config.Path] (usually "~/.docker/cli-plugins").
|
||||
// 2. Additional plugin directories as configured through [ConfigFile.CLIPluginsExtraDirs].
|
||||
// 3. Platform-specific defaultSystemPluginDirs.
|
||||
//
|
||||
// [ConfigFile.CLIPluginsExtraDirs]: https://pkg.go.dev/github.com/docker/cli@v26.1.4+incompatible/cli/config/configfile#ConfigFile.CLIPluginsExtraDirs
|
||||
func getPluginDirs(cfg *configfile.ConfigFile) ([]string, error) {
|
||||
var pluginDirs []string
|
||||
|
||||
|
||||
@ -2,7 +2,19 @@
|
||||
|
||||
package manager
|
||||
|
||||
// defaultSystemPluginDirs are the platform-specific locations to search
|
||||
// for plugins in order of preference.
|
||||
//
|
||||
// Plugin-discovery is performed in the following order of preference:
|
||||
//
|
||||
// 1. The "cli-plugins" directory inside the CLIs config-directory (usually "~/.docker/cli-plugins").
|
||||
// 2. Additional plugin directories as configured through [ConfigFile.CLIPluginsExtraDirs].
|
||||
// 3. Platform-specific defaultSystemPluginDirs (as defined below).
|
||||
//
|
||||
// [ConfigFile.CLIPluginsExtraDirs]: https://pkg.go.dev/github.com/docker/cli@v26.1.4+incompatible/cli/config/configfile#ConfigFile.CLIPluginsExtraDirs
|
||||
var defaultSystemPluginDirs = []string{
|
||||
"/usr/local/lib/docker/cli-plugins", "/usr/local/libexec/docker/cli-plugins",
|
||||
"/usr/lib/docker/cli-plugins", "/usr/libexec/docker/cli-plugins",
|
||||
"/usr/local/lib/docker/cli-plugins",
|
||||
"/usr/local/libexec/docker/cli-plugins",
|
||||
"/usr/lib/docker/cli-plugins",
|
||||
"/usr/libexec/docker/cli-plugins",
|
||||
}
|
||||
|
||||
@ -5,6 +5,16 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// defaultSystemPluginDirs are the platform-specific locations to search
|
||||
// for plugins in order of preference.
|
||||
//
|
||||
// Plugin-discovery is performed in the following order of preference:
|
||||
//
|
||||
// 1. The "cli-plugins" directory inside the CLIs config-directory (usually "~/.docker/cli-plugins").
|
||||
// 2. Additional plugin directories as configured through [ConfigFile.CLIPluginsExtraDirs].
|
||||
// 3. Platform-specific defaultSystemPluginDirs (as defined below).
|
||||
//
|
||||
// [ConfigFile.CLIPluginsExtraDirs]: https://pkg.go.dev/github.com/docker/cli@v26.1.4+incompatible/cli/config/configfile#ConfigFile.CLIPluginsExtraDirs
|
||||
var defaultSystemPluginDirs = []string{
|
||||
filepath.Join(os.Getenv("ProgramData"), "Docker", "cli-plugins"),
|
||||
filepath.Join(os.Getenv("ProgramFiles"), "Docker", "cli-plugins"),
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
@ -105,13 +106,13 @@ func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) {
|
||||
|
||||
// RunHook executes the plugin's hooks command
|
||||
// and returns its unprocessed output.
|
||||
func (p *Plugin) RunHook(hookData HookPluginData) ([]byte, error) {
|
||||
func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte, error) {
|
||||
hDataBytes, err := json.Marshal(hookData)
|
||||
if err != nil {
|
||||
return nil, wrapAsPluginError(err, "failed to marshall hook data")
|
||||
}
|
||||
|
||||
pCmd := exec.Command(p.Path, p.Name, HookSubcommandName, string(hDataBytes))
|
||||
pCmd := exec.CommandContext(ctx, p.Path, p.Name, HookSubcommandName, string(hDataBytes))
|
||||
pCmd.Env = os.Environ()
|
||||
pCmd.Env = append(pCmd.Env, ReexecEnvvar+"="+os.Args[0])
|
||||
hookCmdOutput, err := pCmd.Output()
|
||||
|
||||
@ -36,13 +36,7 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager
|
||||
PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
|
||||
var err error
|
||||
persistentPreRunOnce.Do(func() {
|
||||
cmdContext := cmd.Context()
|
||||
// TODO: revisit and make sure this check makes sense
|
||||
// see: https://github.com/docker/cli/pull/4599#discussion_r1422487271
|
||||
if cmdContext == nil {
|
||||
cmdContext = context.TODO()
|
||||
}
|
||||
ctx, cancel := context.WithCancel(cmdContext)
|
||||
ctx, cancel := context.WithCancel(cmd.Context())
|
||||
cmd.SetContext(ctx)
|
||||
// Set up the context to cancel based on signalling via CLI socket.
|
||||
socket.ConnectAndWait(cancel)
|
||||
@ -51,6 +45,7 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager
|
||||
if os.Getenv("DOCKER_CLI_PLUGIN_USE_DIAL_STDIO") != "" {
|
||||
opts = append(opts, withPluginClientConn(plugin.Name()))
|
||||
}
|
||||
opts = append(opts, command.WithEnableGlobalMeterProvider(), command.WithEnableGlobalTracerProvider())
|
||||
err = tcmd.Initialize(opts...)
|
||||
ogRunE := cmd.RunE
|
||||
if ogRunE == nil {
|
||||
|
||||
@ -9,6 +9,8 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// EnvKey represents the well-known environment variable used to pass the
|
||||
@ -30,6 +32,7 @@ func NewPluginServer(h func(net.Conn)) (*PluginServer, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Trace("Plugin server listening on ", l.Addr())
|
||||
|
||||
if h == nil {
|
||||
h = func(net.Conn) {}
|
||||
@ -92,6 +95,7 @@ func (pl *PluginServer) Addr() net.Addr {
|
||||
//
|
||||
// The error value is that of the underlying [net.Listner.Close] call.
|
||||
func (pl *PluginServer) Close() error {
|
||||
logrus.Trace("Closing plugin server")
|
||||
// Close connections first to ensure the connections get io.EOF instead
|
||||
// of a connection reset.
|
||||
pl.closeAllConns()
|
||||
@ -107,6 +111,10 @@ func (pl *PluginServer) closeAllConns() {
|
||||
pl.mu.Lock()
|
||||
defer pl.mu.Unlock()
|
||||
|
||||
if pl.closed {
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent new connections from being accepted.
|
||||
pl.closed = true
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *c
|
||||
rootCmd.SetHelpCommand(helpCommand)
|
||||
|
||||
rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
|
||||
rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
|
||||
rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "use --help")
|
||||
rootCmd.PersistentFlags().Lookup("help").Hidden = true
|
||||
|
||||
rootCmd.Annotations = map[string]string{
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package command
|
||||
|
||||
@ -44,7 +44,7 @@ const defaultInitTimeout = 2 * time.Second
|
||||
type Streams interface {
|
||||
In() *streams.In
|
||||
Out() *streams.Out
|
||||
Err() io.Writer
|
||||
Err() *streams.Out
|
||||
}
|
||||
|
||||
// Cli represents the docker command line client.
|
||||
@ -75,7 +75,7 @@ type DockerCli struct {
|
||||
options *cliflags.ClientOptions
|
||||
in *streams.In
|
||||
out *streams.Out
|
||||
err io.Writer
|
||||
err *streams.Out
|
||||
client client.APIClient
|
||||
serverInfo ServerInfo
|
||||
contentTrust bool
|
||||
@ -92,6 +92,8 @@ type DockerCli struct {
|
||||
// this may be replaced by explicitly passing a context to functions that
|
||||
// need it.
|
||||
baseCtx context.Context
|
||||
|
||||
enableGlobalMeter, enableGlobalTracer bool
|
||||
}
|
||||
|
||||
// DefaultVersion returns api.defaultVersion.
|
||||
@ -124,7 +126,7 @@ func (cli *DockerCli) Out() *streams.Out {
|
||||
}
|
||||
|
||||
// Err returns the writer used for stderr
|
||||
func (cli *DockerCli) Err() io.Writer {
|
||||
func (cli *DockerCli) Err() *streams.Out {
|
||||
return cli.err
|
||||
}
|
||||
|
||||
@ -184,9 +186,18 @@ func (cli *DockerCli) BuildKitEnabled() (bool, error) {
|
||||
if _, ok := aliasMap["builder"]; ok {
|
||||
return true, nil
|
||||
}
|
||||
// otherwise, assume BuildKit is enabled but
|
||||
// not if wcow reported from server side
|
||||
return cli.ServerInfo().OSType != "windows", nil
|
||||
|
||||
si := cli.ServerInfo()
|
||||
if si.BuildkitVersion == types.BuilderBuildKit {
|
||||
// The daemon advertised BuildKit as the preferred builder; this may
|
||||
// be either a Linux daemon or a Windows daemon with experimental
|
||||
// BuildKit support enabled.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// otherwise, assume BuildKit is enabled for Linux, but disabled for
|
||||
// Windows / WCOW, which does not yet support BuildKit by default.
|
||||
return si.OSType != "windows", nil
|
||||
}
|
||||
|
||||
// HooksEnabled returns whether plugin hooks are enabled.
|
||||
@ -275,8 +286,12 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
||||
}
|
||||
|
||||
// TODO(krissetto): pass ctx to the funcs instead of using this
|
||||
cli.createGlobalMeterProvider(cli.baseCtx)
|
||||
cli.createGlobalTracerProvider(cli.baseCtx)
|
||||
if cli.enableGlobalMeter {
|
||||
cli.createGlobalMeterProvider(cli.baseCtx)
|
||||
}
|
||||
if cli.enableGlobalTracer {
|
||||
cli.createGlobalTracerProvider(cli.baseCtx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -315,7 +330,7 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF
|
||||
|
||||
func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint, error) {
|
||||
if s == nil {
|
||||
return docker.Endpoint{}, fmt.Errorf("no context store initialized")
|
||||
return docker.Endpoint{}, errors.New("no context store initialized")
|
||||
}
|
||||
ctxMeta, err := s.GetMetadata(contextName)
|
||||
if err != nil {
|
||||
@ -546,7 +561,7 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error
|
||||
case 1:
|
||||
host = hosts[0]
|
||||
default:
|
||||
return "", errors.New("Please specify only one -H")
|
||||
return "", errors.New("Specify only one -H")
|
||||
}
|
||||
|
||||
return dopts.ParseHost(tlsOptions != nil, host)
|
||||
|
||||
@ -23,7 +23,7 @@ func WithStandardStreams() CLIOption {
|
||||
stdin, stdout, stderr := term.StdStreams()
|
||||
cli.in = streams.NewIn(stdin)
|
||||
cli.out = streams.NewOut(stdout)
|
||||
cli.err = stderr
|
||||
cli.err = streams.NewOut(stderr)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -40,8 +40,9 @@ func WithBaseContext(ctx context.Context) CLIOption {
|
||||
// WithCombinedStreams uses the same stream for the output and error streams.
|
||||
func WithCombinedStreams(combined io.Writer) CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
cli.out = streams.NewOut(combined)
|
||||
cli.err = combined
|
||||
s := streams.NewOut(combined)
|
||||
cli.out = s
|
||||
cli.err = s
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -65,7 +66,7 @@ func WithOutputStream(out io.Writer) CLIOption {
|
||||
// WithErrorStream sets a cli error stream.
|
||||
func WithErrorStream(err io.Writer) CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
cli.err = err
|
||||
cli.err = streams.NewOut(err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ import (
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
@ -192,7 +193,7 @@ func TestInitializeFromClientHangs(t *testing.T) {
|
||||
ts.Start()
|
||||
defer ts.Close()
|
||||
|
||||
opts := &flags.ClientOptions{Hosts: []string{fmt.Sprintf("unix://%s", socket)}}
|
||||
opts := &flags.ClientOptions{Hosts: []string{"unix://" + socket}}
|
||||
configFile := &configfile.ConfigFile{}
|
||||
apiClient, err := NewAPIClientFromFlags(opts, configFile)
|
||||
assert.NilError(t, err)
|
||||
@ -253,7 +254,7 @@ func TestExperimentalCLI(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
cli := &DockerCli{client: apiclient, err: os.Stderr}
|
||||
cli := &DockerCli{client: apiclient, err: streams.NewOut(os.Stderr)}
|
||||
config.SetDir(dir.Path())
|
||||
err := cli.Initialize(flags.NewClientOptions())
|
||||
assert.NilError(t, err)
|
||||
|
||||
@ -3,28 +3,39 @@ package completion
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types"
|
||||
"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/volume"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ValidArgsFn a function to be used by cobra command as `ValidArgsFunction` to offer command line completion
|
||||
type ValidArgsFn func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective)
|
||||
|
||||
// APIClientProvider provides a method to get an [client.APIClient], initializing
|
||||
// it if needed.
|
||||
//
|
||||
// It's a smaller interface than [command.Cli], and used in situations where an
|
||||
// APIClient is needed, but we want to postpone initializing the client until
|
||||
// it's used.
|
||||
type APIClientProvider interface {
|
||||
Client() client.APIClient
|
||||
}
|
||||
|
||||
// ImageNames offers completion for images present within the local store
|
||||
func ImageNames(dockerCli command.Cli) ValidArgsFn {
|
||||
func ImageNames(dockerCLI APIClientProvider) ValidArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list, err := dockerCli.Client().ImageList(cmd.Context(), image.ListOptions{})
|
||||
list, err := dockerCLI.Client().ImageList(cmd.Context(), image.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
var names []string
|
||||
for _, image := range list {
|
||||
names = append(names, image.RepoTags...)
|
||||
for _, img := range list {
|
||||
names = append(names, img.RepoTags...)
|
||||
}
|
||||
return names, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
@ -33,9 +44,9 @@ func ImageNames(dockerCli command.Cli) ValidArgsFn {
|
||||
// ContainerNames offers completion for container names and IDs
|
||||
// By default, only names are returned.
|
||||
// Set DOCKER_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs.
|
||||
func ContainerNames(dockerCli command.Cli, all bool, filters ...func(types.Container) bool) ValidArgsFn {
|
||||
func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(types.Container) bool) ValidArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list, err := dockerCli.Client().ContainerList(cmd.Context(), container.ListOptions{
|
||||
list, err := dockerCLI.Client().ContainerList(cmd.Context(), container.ListOptions{
|
||||
All: all,
|
||||
})
|
||||
if err != nil {
|
||||
@ -45,10 +56,10 @@ func ContainerNames(dockerCli command.Cli, all bool, filters ...func(types.Conta
|
||||
showContainerIDs := os.Getenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS") == "yes"
|
||||
|
||||
var names []string
|
||||
for _, container := range list {
|
||||
for _, ctr := range list {
|
||||
skip := false
|
||||
for _, fn := range filters {
|
||||
if !fn(container) {
|
||||
if !fn(ctr) {
|
||||
skip = true
|
||||
break
|
||||
}
|
||||
@ -57,18 +68,18 @@ func ContainerNames(dockerCli command.Cli, all bool, filters ...func(types.Conta
|
||||
continue
|
||||
}
|
||||
if showContainerIDs {
|
||||
names = append(names, container.ID)
|
||||
names = append(names, ctr.ID)
|
||||
}
|
||||
names = append(names, formatter.StripNamePrefix(container.Names)...)
|
||||
names = append(names, formatter.StripNamePrefix(ctr.Names)...)
|
||||
}
|
||||
return names, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
// VolumeNames offers completion for volumes
|
||||
func VolumeNames(dockerCli command.Cli) ValidArgsFn {
|
||||
func VolumeNames(dockerCLI APIClientProvider) ValidArgsFn {
|
||||
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(), volume.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
@ -81,21 +92,21 @@ func VolumeNames(dockerCli command.Cli) ValidArgsFn {
|
||||
}
|
||||
|
||||
// NetworkNames offers completion for networks
|
||||
func NetworkNames(dockerCli command.Cli) ValidArgsFn {
|
||||
func NetworkNames(dockerCLI APIClientProvider) ValidArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list, err := dockerCli.Client().NetworkList(cmd.Context(), types.NetworkListOptions{})
|
||||
list, err := dockerCLI.Client().NetworkList(cmd.Context(), network.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
var names []string
|
||||
for _, network := range list {
|
||||
names = append(names, network.Name)
|
||||
for _, nw := range list {
|
||||
names = append(names, nw.Name)
|
||||
}
|
||||
return names, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
// NoComplete is used for commands where there's no relevant completion
|
||||
func NoComplete(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
|
||||
func NoComplete(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
@ -30,9 +30,9 @@ func NewConfigCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
// completeNames offers completion for swarm configs
|
||||
func completeNames(dockerCli command.Cli) completion.ValidArgsFn {
|
||||
func completeNames(dockerCLI completion.APIClientProvider) completion.ValidArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list, err := dockerCli.Client().ConfigList(cmd.Context(), types.ConfigListOptions{})
|
||||
list, err := dockerCLI.Client().ConfigList(cmd.Context(), types.ConfigListOptions{})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package config
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@ package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
@ -105,7 +104,12 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
|
||||
|
||||
if opts.Proxy && !c.Config.Tty {
|
||||
sigc := notifyAllSignals()
|
||||
go ForwardAllSignals(ctx, apiClient, containerID, sigc)
|
||||
// since we're explicitly setting up signal handling here, and the daemon will
|
||||
// get notified independently of the clients ctx cancellation, we use this context
|
||||
// but without cancellation to avoid ForwardAllSignals from returning
|
||||
// before all signals are forwarded.
|
||||
bgCtx := context.WithoutCancel(ctx)
|
||||
go ForwardAllSignals(bgCtx, apiClient, containerID, sigc)
|
||||
defer signal.StopCatch(sigc)
|
||||
}
|
||||
|
||||
@ -153,7 +157,7 @@ func getExitStatus(errC <-chan error, resultC <-chan container.WaitResponse) err
|
||||
select {
|
||||
case result := <-resultC:
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf(result.Error.Message)
|
||||
return errors.New(result.Error.Message)
|
||||
}
|
||||
if result.StatusCode != 0 {
|
||||
return cli.StatusError{StatusCode: int(result.StatusCode)}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
@ -79,7 +78,7 @@ func TestNewAttachCommandErrors(t *testing.T) {
|
||||
|
||||
func TestGetExitStatus(t *testing.T) {
|
||||
var (
|
||||
expectedErr = fmt.Errorf("unexpected error")
|
||||
expectedErr = errors.New("unexpected error")
|
||||
errC = make(chan error, 1)
|
||||
resultC = make(chan container.WaitResponse, 1)
|
||||
)
|
||||
|
||||
@ -17,8 +17,8 @@ import (
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
inspectFunc func(string) (types.ContainerJSON, error)
|
||||
execInspectFunc func(execID string) (types.ContainerExecInspect, error)
|
||||
execCreateFunc func(containerID string, config types.ExecConfig) (types.IDResponse, error)
|
||||
execInspectFunc func(execID string) (container.ExecInspect, error)
|
||||
execCreateFunc func(containerID string, options container.ExecOptions) (types.IDResponse, error)
|
||||
createContainerFunc func(config *container.Config,
|
||||
hostConfig *container.HostConfig,
|
||||
networkingConfig *network.NetworkingConfig,
|
||||
@ -27,8 +27,8 @@ type fakeClient struct {
|
||||
containerStartFunc func(containerID string, options container.StartOptions) error
|
||||
imageCreateFunc func(parentReference string, options image.CreateOptions) (io.ReadCloser, error)
|
||||
infoFunc func() (system.Info, error)
|
||||
containerStatPathFunc func(containerID, path string) (types.ContainerPathStat, error)
|
||||
containerCopyFromFunc func(containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
|
||||
containerStatPathFunc func(containerID, path string) (container.PathStat, error)
|
||||
containerCopyFromFunc func(containerID, srcPath string) (io.ReadCloser, container.PathStat, error)
|
||||
logFunc func(string, container.LogsOptions) (io.ReadCloser, error)
|
||||
waitFunc func(string) (<-chan container.WaitResponse, <-chan error)
|
||||
containerListFunc func(container.ListOptions) ([]types.Container, error)
|
||||
@ -36,7 +36,8 @@ type fakeClient struct {
|
||||
containerExecResizeFunc func(id string, options container.ResizeOptions) error
|
||||
containerRemoveFunc func(ctx context.Context, containerID string, options container.RemoveOptions) error
|
||||
containerKillFunc func(ctx context.Context, containerID, signal string) error
|
||||
containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, 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)
|
||||
Version string
|
||||
}
|
||||
|
||||
@ -54,21 +55,21 @@ func (f *fakeClient) ContainerInspect(_ context.Context, containerID string) (ty
|
||||
return types.ContainerJSON{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerExecCreate(_ context.Context, containerID string, config types.ExecConfig) (types.IDResponse, error) {
|
||||
func (f *fakeClient) ContainerExecCreate(_ context.Context, containerID string, config container.ExecOptions) (types.IDResponse, error) {
|
||||
if f.execCreateFunc != nil {
|
||||
return f.execCreateFunc(containerID, config)
|
||||
}
|
||||
return types.IDResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerExecInspect(_ context.Context, execID string) (types.ContainerExecInspect, error) {
|
||||
func (f *fakeClient) ContainerExecInspect(_ context.Context, execID string) (container.ExecInspect, error) {
|
||||
if f.execInspectFunc != nil {
|
||||
return f.execInspectFunc(execID)
|
||||
}
|
||||
return types.ContainerExecInspect{}, nil
|
||||
return container.ExecInspect{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerExecStart(context.Context, string, types.ExecStartCheck) error {
|
||||
func (f *fakeClient) ContainerExecStart(context.Context, string, container.ExecStartOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -107,18 +108,18 @@ func (f *fakeClient) Info(_ context.Context) (system.Info, error) {
|
||||
return system.Info{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerStatPath(_ context.Context, containerID, path string) (types.ContainerPathStat, error) {
|
||||
func (f *fakeClient) ContainerStatPath(_ context.Context, containerID, path string) (container.PathStat, error) {
|
||||
if f.containerStatPathFunc != nil {
|
||||
return f.containerStatPathFunc(containerID, path)
|
||||
}
|
||||
return types.ContainerPathStat{}, nil
|
||||
return container.PathStat{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) CopyFromContainer(_ context.Context, containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
func (f *fakeClient) CopyFromContainer(_ context.Context, containerID, srcPath string) (io.ReadCloser, container.PathStat, error) {
|
||||
if f.containerCopyFromFunc != nil {
|
||||
return f.containerCopyFromFunc(containerID, srcPath)
|
||||
}
|
||||
return nil, types.ContainerPathStat{}, nil
|
||||
return nil, container.PathStat{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerLogs(_ context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
|
||||
@ -167,9 +168,16 @@ func (f *fakeClient) ContainerKill(ctx context.Context, containerID, signal stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error) {
|
||||
func (f *fakeClient) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
|
||||
if f.containerPruneFunc != nil {
|
||||
return f.containerPruneFunc(ctx, pruneFilters)
|
||||
}
|
||||
return types.ContainersPruneReport{}, nil
|
||||
return container.PruneReport{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerAttach(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
|
||||
if f.containerAttachFunc != nil {
|
||||
return f.containerAttachFunc(ctx, containerID, options)
|
||||
}
|
||||
return types.HijackedResponse{}, nil
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
units "github.com/docker/go-units"
|
||||
@ -397,7 +397,7 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo
|
||||
}
|
||||
}
|
||||
|
||||
options := types.CopyToContainerOptions{
|
||||
options := container.CopyToContainerOptions{
|
||||
AllowOverwriteDirWithFile: false,
|
||||
CopyUIDGID: copyConfig.copyUIDGID,
|
||||
}
|
||||
@ -433,18 +433,18 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo
|
||||
// so we have to check for a `/` or `.` prefix. Also, in the case of a Windows
|
||||
// client, a `:` could be part of an absolute Windows path, in which case it
|
||||
// is immediately proceeded by a backslash.
|
||||
func splitCpArg(arg string) (container, path string) {
|
||||
func splitCpArg(arg string) (ctr, path string) {
|
||||
if system.IsAbs(arg) {
|
||||
// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
|
||||
return "", arg
|
||||
}
|
||||
|
||||
container, path, ok := strings.Cut(arg, ":")
|
||||
if !ok || strings.HasPrefix(container, ".") {
|
||||
ctr, path, ok := strings.Cut(arg, ":")
|
||||
if !ok || strings.HasPrefix(ctr, ".") {
|
||||
// Either there's no `:` in the arg
|
||||
// OR it's an explicit local relative path like `./file:name.txt`.
|
||||
return "", arg
|
||||
}
|
||||
|
||||
return container, path
|
||||
return ctr, path
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@ -51,15 +51,16 @@ func TestRunCopyWithInvalidArguments(t *testing.T) {
|
||||
func TestRunCopyFromContainerToStdout(t *testing.T) {
|
||||
tarContent := "the tar content"
|
||||
|
||||
fakeClient := &fakeClient{
|
||||
containerCopyFromFunc: func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
assert.Check(t, is.Equal("container", container))
|
||||
return io.NopCloser(strings.NewReader(tarContent)), types.ContainerPathStat{}, nil
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
|
||||
assert.Check(t, is.Equal("container", ctr))
|
||||
return io.NopCloser(strings.NewReader(tarContent)), container.PathStat{}, nil
|
||||
},
|
||||
}
|
||||
options := copyOptions{source: "container:/path", destination: "-"}
|
||||
cli := test.NewFakeCli(fakeClient)
|
||||
err := runCopy(context.TODO(), cli, options)
|
||||
})
|
||||
err := runCopy(context.TODO(), cli, copyOptions{
|
||||
source: "container:/path",
|
||||
destination: "-",
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(tarContent, cli.OutBuffer().String()))
|
||||
assert.Check(t, is.Equal("", cli.ErrBuffer().String()))
|
||||
@ -70,16 +71,18 @@ func TestRunCopyFromContainerToFilesystem(t *testing.T) {
|
||||
fs.WithFile("file1", "content\n"))
|
||||
defer destDir.Remove()
|
||||
|
||||
fakeClient := &fakeClient{
|
||||
containerCopyFromFunc: func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
assert.Check(t, is.Equal("container", container))
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
|
||||
assert.Check(t, is.Equal("container", ctr))
|
||||
readCloser, err := archive.TarWithOptions(destDir.Path(), &archive.TarOptions{})
|
||||
return readCloser, types.ContainerPathStat{}, err
|
||||
return readCloser, container.PathStat{}, err
|
||||
},
|
||||
}
|
||||
options := copyOptions{source: "container:/path", destination: destDir.Path(), quiet: true}
|
||||
cli := test.NewFakeCli(fakeClient)
|
||||
err := runCopy(context.TODO(), cli, options)
|
||||
})
|
||||
err := runCopy(context.TODO(), cli, copyOptions{
|
||||
source: "container:/path",
|
||||
destination: destDir.Path(),
|
||||
quiet: true,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal("", cli.OutBuffer().String()))
|
||||
assert.Check(t, is.Equal("", cli.ErrBuffer().String()))
|
||||
@ -94,20 +97,17 @@ func TestRunCopyFromContainerToFilesystemMissingDestinationDirectory(t *testing.
|
||||
fs.WithFile("file1", "content\n"))
|
||||
defer destDir.Remove()
|
||||
|
||||
fakeClient := &fakeClient{
|
||||
containerCopyFromFunc: func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
assert.Check(t, is.Equal("container", container))
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
|
||||
assert.Check(t, is.Equal("container", ctr))
|
||||
readCloser, err := archive.TarWithOptions(destDir.Path(), &archive.TarOptions{})
|
||||
return readCloser, types.ContainerPathStat{}, err
|
||||
return readCloser, container.PathStat{}, err
|
||||
},
|
||||
}
|
||||
|
||||
options := copyOptions{
|
||||
})
|
||||
err := runCopy(context.TODO(), cli, copyOptions{
|
||||
source: "container:/path",
|
||||
destination: destDir.Join("missing", "foo"),
|
||||
}
|
||||
cli := test.NewFakeCli(fakeClient)
|
||||
err := runCopy(context.TODO(), cli, options)
|
||||
})
|
||||
assert.ErrorContains(t, err, destDir.Join("missing"))
|
||||
}
|
||||
|
||||
@ -115,12 +115,11 @@ func TestRunCopyToContainerFromFileWithTrailingSlash(t *testing.T) {
|
||||
srcFile := fs.NewFile(t, t.Name())
|
||||
defer srcFile.Remove()
|
||||
|
||||
options := copyOptions{
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
err := runCopy(context.TODO(), cli, copyOptions{
|
||||
source: srcFile.Path() + string(os.PathSeparator),
|
||||
destination: "container:/path",
|
||||
}
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
err := runCopy(context.TODO(), cli, options)
|
||||
})
|
||||
|
||||
expectedError := "not a directory"
|
||||
if runtime.GOOS == "windows" {
|
||||
@ -130,12 +129,11 @@ func TestRunCopyToContainerFromFileWithTrailingSlash(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRunCopyToContainerSourceDoesNotExist(t *testing.T) {
|
||||
options := copyOptions{
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
err := runCopy(context.TODO(), cli, copyOptions{
|
||||
source: "/does/not/exist",
|
||||
destination: "container:/path",
|
||||
}
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
err := runCopy(context.TODO(), cli, options)
|
||||
})
|
||||
expected := "no such file or directory"
|
||||
if runtime.GOOS == "windows" {
|
||||
expected = "cannot find the file specified"
|
||||
@ -184,17 +182,19 @@ func TestSplitCpArg(t *testing.T) {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
skip.If(t, testcase.os != "" && testcase.os != runtime.GOOS)
|
||||
|
||||
container, path := splitCpArg(testcase.path)
|
||||
assert.Check(t, is.Equal(testcase.expectedContainer, container))
|
||||
ctr, path := splitCpArg(testcase.path)
|
||||
assert.Check(t, is.Equal(testcase.expectedContainer, ctr))
|
||||
assert.Check(t, is.Equal(testcase.expectedPath, path))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCopyFromContainerToFilesystemIrregularDestination(t *testing.T) {
|
||||
options := copyOptions{source: "container:/dev/null", destination: "/dev/random"}
|
||||
cli := test.NewFakeCli(nil)
|
||||
err := runCopy(context.TODO(), cli, options)
|
||||
err := runCopy(context.TODO(), cli, copyOptions{
|
||||
source: "container:/dev/null",
|
||||
destination: "/dev/random",
|
||||
})
|
||||
assert.Assert(t, err != nil)
|
||||
expected := `"/dev/random" must be a directory or a regular file`
|
||||
assert.ErrorContains(t, err, expected)
|
||||
|
||||
@ -130,9 +130,9 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
|
||||
|
||||
out := dockerCli.Err()
|
||||
if options.quiet {
|
||||
out = io.Discard
|
||||
out = streams.NewOut(io.Discard)
|
||||
}
|
||||
return jsonmessage.DisplayJSONMessagesToStream(responseBody, streams.NewOut(out), nil)
|
||||
return jsonmessage.DisplayJSONMessagesToStream(responseBody, out, nil)
|
||||
}
|
||||
|
||||
type cidFile struct {
|
||||
|
||||
@ -3,7 +3,6 @@ package container
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
@ -231,7 +230,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
||||
platform *specs.Platform,
|
||||
containerName string,
|
||||
) (container.CreateResponse, error) {
|
||||
return container.CreateResponse{}, fmt.Errorf("shouldn't try to pull image")
|
||||
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
fakeCLI.SetNotaryClient(tc.notaryFunc)
|
||||
|
||||
@ -12,7 +12,8 @@ import (
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
apiclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@ -43,19 +44,18 @@ func NewExecOptions() ExecOptions {
|
||||
// NewExecCommand creates a new cobra.Command for `docker exec`
|
||||
func NewExecCommand(dockerCli command.Cli) *cobra.Command {
|
||||
options := NewExecOptions()
|
||||
var container string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "exec [OPTIONS] CONTAINER COMMAND [ARG...]",
|
||||
Short: "Execute a command in a running container",
|
||||
Args: cli.RequiresMinArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
container = args[0]
|
||||
containerIDorName := args[0]
|
||||
options.Command = args[1:]
|
||||
return RunExec(cmd.Context(), dockerCli, container, options)
|
||||
return RunExec(cmd.Context(), dockerCli, containerIDorName, options)
|
||||
},
|
||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(container types.Container) bool {
|
||||
return container.State != "paused"
|
||||
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(ctr types.Container) bool {
|
||||
return ctr.State != "paused"
|
||||
}),
|
||||
Annotations: map[string]string{
|
||||
"category-top": "2",
|
||||
@ -79,47 +79,41 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.StringVarP(&options.Workdir, "workdir", "w", "", "Working directory inside the container")
|
||||
flags.SetAnnotation("workdir", "version", []string{"1.35"})
|
||||
|
||||
cmd.RegisterFlagCompletionFunc(
|
||||
"env",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return os.Environ(), cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
)
|
||||
cmd.RegisterFlagCompletionFunc(
|
||||
"env-file",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveDefault // _filedir
|
||||
},
|
||||
)
|
||||
_ = cmd.RegisterFlagCompletionFunc("env", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
|
||||
return os.Environ(), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
_ = cmd.RegisterFlagCompletionFunc("env-file", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveDefault // _filedir
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunExec executes an `exec` command
|
||||
func RunExec(ctx context.Context, dockerCli command.Cli, container string, options ExecOptions) error {
|
||||
execConfig, err := parseExec(options, dockerCli.ConfigFile())
|
||||
func RunExec(ctx context.Context, dockerCli command.Cli, containerIDorName string, options ExecOptions) error {
|
||||
execOptions, err := parseExec(options, dockerCli.ConfigFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := dockerCli.Client()
|
||||
apiClient := dockerCli.Client()
|
||||
|
||||
// We need to check the tty _before_ we do the ContainerExecCreate, because
|
||||
// otherwise if we error out we will leak execIDs on the server (and
|
||||
// there's no easy way to clean those up). But also in order to make "not
|
||||
// exist" errors take precedence we do a dummy inspect first.
|
||||
if _, err := client.ContainerInspect(ctx, container); err != nil {
|
||||
if _, err := apiClient.ContainerInspect(ctx, containerIDorName); err != nil {
|
||||
return err
|
||||
}
|
||||
if !execConfig.Detach {
|
||||
if err := dockerCli.In().CheckTty(execConfig.AttachStdin, execConfig.Tty); err != nil {
|
||||
if !execOptions.Detach {
|
||||
if err := dockerCli.In().CheckTty(execOptions.AttachStdin, execOptions.Tty); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fillConsoleSize(execConfig, dockerCli)
|
||||
fillConsoleSize(execOptions, dockerCli)
|
||||
|
||||
response, err := client.ContainerExecCreate(ctx, container, *execConfig)
|
||||
response, err := apiClient.ContainerExecCreate(ctx, containerIDorName, *execOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -129,52 +123,50 @@ func RunExec(ctx context.Context, dockerCli command.Cli, container string, optio
|
||||
return errors.New("exec ID empty")
|
||||
}
|
||||
|
||||
if execConfig.Detach {
|
||||
execStartCheck := types.ExecStartCheck{
|
||||
Detach: execConfig.Detach,
|
||||
Tty: execConfig.Tty,
|
||||
ConsoleSize: execConfig.ConsoleSize,
|
||||
}
|
||||
return client.ContainerExecStart(ctx, execID, execStartCheck)
|
||||
if execOptions.Detach {
|
||||
return apiClient.ContainerExecStart(ctx, execID, container.ExecStartOptions{
|
||||
Detach: execOptions.Detach,
|
||||
Tty: execOptions.Tty,
|
||||
ConsoleSize: execOptions.ConsoleSize,
|
||||
})
|
||||
}
|
||||
return interactiveExec(ctx, dockerCli, execConfig, execID)
|
||||
return interactiveExec(ctx, dockerCli, execOptions, execID)
|
||||
}
|
||||
|
||||
func fillConsoleSize(execConfig *types.ExecConfig, dockerCli command.Cli) {
|
||||
if execConfig.Tty {
|
||||
func fillConsoleSize(execOptions *container.ExecOptions, dockerCli command.Cli) {
|
||||
if execOptions.Tty {
|
||||
height, width := dockerCli.Out().GetTtySize()
|
||||
execConfig.ConsoleSize = &[2]uint{height, width}
|
||||
execOptions.ConsoleSize = &[2]uint{height, width}
|
||||
}
|
||||
}
|
||||
|
||||
func interactiveExec(ctx context.Context, dockerCli command.Cli, execConfig *types.ExecConfig, execID string) error {
|
||||
func interactiveExec(ctx context.Context, dockerCli command.Cli, execOptions *container.ExecOptions, execID string) error {
|
||||
// Interactive exec requested.
|
||||
var (
|
||||
out, stderr io.Writer
|
||||
in io.ReadCloser
|
||||
)
|
||||
|
||||
if execConfig.AttachStdin {
|
||||
if execOptions.AttachStdin {
|
||||
in = dockerCli.In()
|
||||
}
|
||||
if execConfig.AttachStdout {
|
||||
if execOptions.AttachStdout {
|
||||
out = dockerCli.Out()
|
||||
}
|
||||
if execConfig.AttachStderr {
|
||||
if execConfig.Tty {
|
||||
if execOptions.AttachStderr {
|
||||
if execOptions.Tty {
|
||||
stderr = dockerCli.Out()
|
||||
} else {
|
||||
stderr = dockerCli.Err()
|
||||
}
|
||||
}
|
||||
fillConsoleSize(execConfig, dockerCli)
|
||||
fillConsoleSize(execOptions, dockerCli)
|
||||
|
||||
client := dockerCli.Client()
|
||||
execStartCheck := types.ExecStartCheck{
|
||||
Tty: execConfig.Tty,
|
||||
ConsoleSize: execConfig.ConsoleSize,
|
||||
}
|
||||
resp, err := client.ContainerExecAttach(ctx, execID, execStartCheck)
|
||||
apiClient := dockerCli.Client()
|
||||
resp, err := apiClient.ContainerExecAttach(ctx, execID, container.ExecAttachOptions{
|
||||
Tty: execOptions.Tty,
|
||||
ConsoleSize: execOptions.ConsoleSize,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -191,17 +183,17 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execConfig *typ
|
||||
outputStream: out,
|
||||
errorStream: stderr,
|
||||
resp: resp,
|
||||
tty: execConfig.Tty,
|
||||
detachKeys: execConfig.DetachKeys,
|
||||
tty: execOptions.Tty,
|
||||
detachKeys: execOptions.DetachKeys,
|
||||
}
|
||||
|
||||
return streamer.stream(ctx)
|
||||
}()
|
||||
}()
|
||||
|
||||
if execConfig.Tty && dockerCli.In().IsTerminal() {
|
||||
if execOptions.Tty && dockerCli.In().IsTerminal() {
|
||||
if err := MonitorTtySize(ctx, dockerCli, execID, true); err != nil {
|
||||
fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
|
||||
_, _ = fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,14 +202,14 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execConfig *typ
|
||||
return err
|
||||
}
|
||||
|
||||
return getExecExitStatus(ctx, client, execID)
|
||||
return getExecExitStatus(ctx, apiClient, execID)
|
||||
}
|
||||
|
||||
func getExecExitStatus(ctx context.Context, client apiclient.ContainerAPIClient, execID string) error {
|
||||
resp, err := client.ContainerExecInspect(ctx, execID)
|
||||
func getExecExitStatus(ctx context.Context, apiClient client.ContainerAPIClient, execID string) error {
|
||||
resp, err := apiClient.ContainerExecInspect(ctx, execID)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if !apiclient.IsErrConnectionFailed(err) {
|
||||
if !client.IsErrConnectionFailed(err) {
|
||||
return err
|
||||
}
|
||||
return cli.StatusError{StatusCode: -1}
|
||||
@ -231,8 +223,8 @@ func getExecExitStatus(ctx context.Context, client apiclient.ContainerAPIClient,
|
||||
|
||||
// parseExec parses the specified args for the specified command and generates
|
||||
// an ExecConfig from it.
|
||||
func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*types.ExecConfig, error) {
|
||||
execConfig := &types.ExecConfig{
|
||||
func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*container.ExecOptions, error) {
|
||||
execOptions := &container.ExecOptions{
|
||||
User: execOpts.User,
|
||||
Privileged: execOpts.Privileged,
|
||||
Tty: execOpts.TTY,
|
||||
@ -243,23 +235,23 @@ func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*types.
|
||||
|
||||
// collect all the environment variables for the container
|
||||
var err error
|
||||
if execConfig.Env, err = opts.ReadKVEnvStrings(execOpts.EnvFile.GetAll(), execOpts.Env.GetAll()); err != nil {
|
||||
if execOptions.Env, err = opts.ReadKVEnvStrings(execOpts.EnvFile.GetAll(), execOpts.Env.GetAll()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If -d is not set, attach to everything by default
|
||||
if !execOpts.Detach {
|
||||
execConfig.AttachStdout = true
|
||||
execConfig.AttachStderr = true
|
||||
execOptions.AttachStdout = true
|
||||
execOptions.AttachStderr = true
|
||||
if execOpts.Interactive {
|
||||
execConfig.AttachStdin = true
|
||||
execOptions.AttachStdin = true
|
||||
}
|
||||
}
|
||||
|
||||
if execOpts.DetachKeys != "" {
|
||||
execConfig.DetachKeys = execOpts.DetachKeys
|
||||
execOptions.DetachKeys = execOpts.DetachKeys
|
||||
} else {
|
||||
execConfig.DetachKeys = configFile.DetachKeys
|
||||
execOptions.DetachKeys = configFile.DetachKeys
|
||||
}
|
||||
return execConfig, nil
|
||||
return execOptions, nil
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@ -37,10 +38,10 @@ TWO=2
|
||||
testcases := []struct {
|
||||
options ExecOptions
|
||||
configFile configfile.ConfigFile
|
||||
expected types.ExecConfig
|
||||
expected container.ExecOptions
|
||||
}{
|
||||
{
|
||||
expected: types.ExecConfig{
|
||||
expected: container.ExecOptions{
|
||||
Cmd: []string{"command"},
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
@ -48,7 +49,7 @@ TWO=2
|
||||
options: withDefaultOpts(ExecOptions{}),
|
||||
},
|
||||
{
|
||||
expected: types.ExecConfig{
|
||||
expected: container.ExecOptions{
|
||||
Cmd: []string{"command1", "command2"},
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
@ -63,7 +64,7 @@ TWO=2
|
||||
TTY: true,
|
||||
User: "uid",
|
||||
}),
|
||||
expected: types.ExecConfig{
|
||||
expected: container.ExecOptions{
|
||||
User: "uid",
|
||||
AttachStdin: true,
|
||||
AttachStdout: true,
|
||||
@ -74,7 +75,7 @@ TWO=2
|
||||
},
|
||||
{
|
||||
options: withDefaultOpts(ExecOptions{Detach: true}),
|
||||
expected: types.ExecConfig{
|
||||
expected: container.ExecOptions{
|
||||
Detach: true,
|
||||
Cmd: []string{"command"},
|
||||
},
|
||||
@ -85,7 +86,7 @@ TWO=2
|
||||
Interactive: true,
|
||||
Detach: true,
|
||||
}),
|
||||
expected: types.ExecConfig{
|
||||
expected: container.ExecOptions{
|
||||
Detach: true,
|
||||
Tty: true,
|
||||
Cmd: []string{"command"},
|
||||
@ -94,7 +95,7 @@ TWO=2
|
||||
{
|
||||
options: withDefaultOpts(ExecOptions{Detach: true}),
|
||||
configFile: configfile.ConfigFile{DetachKeys: "de"},
|
||||
expected: types.ExecConfig{
|
||||
expected: container.ExecOptions{
|
||||
Cmd: []string{"command"},
|
||||
DetachKeys: "de",
|
||||
Detach: true,
|
||||
@ -106,14 +107,14 @@ TWO=2
|
||||
DetachKeys: "ab",
|
||||
}),
|
||||
configFile: configfile.ConfigFile{DetachKeys: "de"},
|
||||
expected: types.ExecConfig{
|
||||
expected: container.ExecOptions{
|
||||
Cmd: []string{"command"},
|
||||
DetachKeys: "ab",
|
||||
Detach: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
expected: types.ExecConfig{
|
||||
expected: container.ExecOptions{
|
||||
Cmd: []string{"command"},
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
@ -126,7 +127,7 @@ TWO=2
|
||||
}(),
|
||||
},
|
||||
{
|
||||
expected: types.ExecConfig{
|
||||
expected: container.ExecOptions{
|
||||
Cmd: []string{"command"},
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
@ -161,7 +162,7 @@ func TestRunExec(t *testing.T) {
|
||||
testcases := []struct {
|
||||
doc string
|
||||
options ExecOptions
|
||||
client fakeClient
|
||||
client *fakeClient
|
||||
expectedError string
|
||||
expectedOut string
|
||||
expectedErr string
|
||||
@ -171,12 +172,12 @@ func TestRunExec(t *testing.T) {
|
||||
options: withDefaultOpts(ExecOptions{
|
||||
Detach: true,
|
||||
}),
|
||||
client: fakeClient{execCreateFunc: execCreateWithID},
|
||||
client: &fakeClient{execCreateFunc: execCreateWithID},
|
||||
},
|
||||
{
|
||||
doc: "inspect error",
|
||||
options: NewExecOptions(),
|
||||
client: fakeClient{
|
||||
client: &fakeClient{
|
||||
inspectFunc: func(string) (types.ContainerJSON, error) {
|
||||
return types.ContainerJSON{}, errors.New("failed inspect")
|
||||
},
|
||||
@ -187,12 +188,13 @@ func TestRunExec(t *testing.T) {
|
||||
doc: "missing exec ID",
|
||||
options: NewExecOptions(),
|
||||
expectedError: "exec ID empty",
|
||||
client: &fakeClient{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
fakeCLI := test.NewFakeCli(&testcase.client)
|
||||
fakeCLI := test.NewFakeCli(testcase.client)
|
||||
|
||||
err := RunExec(context.TODO(), fakeCLI, "thecontainer", testcase.options)
|
||||
if testcase.expectedError != "" {
|
||||
@ -206,7 +208,7 @@ func TestRunExec(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func execCreateWithID(_ string, _ types.ExecConfig) (types.IDResponse, error) {
|
||||
func execCreateWithID(_ string, _ container.ExecOptions) (types.IDResponse, error) {
|
||||
return types.IDResponse{ID: "execid"}, nil
|
||||
}
|
||||
|
||||
@ -235,9 +237,9 @@ func TestGetExecExitStatus(t *testing.T) {
|
||||
|
||||
for _, testcase := range testcases {
|
||||
client := &fakeClient{
|
||||
execInspectFunc: func(id string) (types.ContainerExecInspect, error) {
|
||||
execInspectFunc: func(id string) (container.ExecInspect, error) {
|
||||
assert.Check(t, is.Equal(execID, id))
|
||||
return types.ContainerExecInspect{ExitCode: testcase.exitCode}, testcase.inspectError
|
||||
return container.ExecInspect{ExitCode: testcase.exitCode}, testcase.inspectError
|
||||
},
|
||||
}
|
||||
err := getExecExitStatus(context.Background(), client, execID)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package container
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
@ -147,7 +147,7 @@ func TestContainerListErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
containerListFunc: func(_ container.ListOptions) ([]types.Container, error) {
|
||||
return nil, fmt.Errorf("error listing containers")
|
||||
return nil, errors.New("error listing containers")
|
||||
},
|
||||
expectedError: "error listing containers",
|
||||
},
|
||||
|
||||
@ -30,7 +30,7 @@ func TestRunLogs(t *testing.T) {
|
||||
testcases := []struct {
|
||||
doc string
|
||||
options *logsOptions
|
||||
client fakeClient
|
||||
client *fakeClient
|
||||
expectedError string
|
||||
expectedOut string
|
||||
expectedErr string
|
||||
@ -39,13 +39,13 @@ func TestRunLogs(t *testing.T) {
|
||||
doc: "successful logs",
|
||||
expectedOut: "foo",
|
||||
options: &logsOptions{},
|
||||
client: fakeClient{logFunc: logFn("foo"), inspectFunc: inspectFn},
|
||||
client: &fakeClient{logFunc: logFn("foo"), inspectFunc: inspectFn},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&testcase.client)
|
||||
cli := test.NewFakeCli(testcase.client)
|
||||
|
||||
err := runLogs(context.TODO(), cli, testcase.options)
|
||||
if testcase.expectedError != "" {
|
||||
|
||||
@ -208,7 +208,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
|
||||
flags.Var(copts.ulimits, "ulimit", "Ulimit options")
|
||||
flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
|
||||
flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container")
|
||||
flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits")
|
||||
flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container and its associated anonymous volumes when it exits")
|
||||
|
||||
// Security
|
||||
flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities")
|
||||
@ -574,10 +574,10 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
return nil, errors.Errorf("--health-retries cannot be negative")
|
||||
}
|
||||
if copts.healthStartPeriod < 0 {
|
||||
return nil, fmt.Errorf("--health-start-period cannot be negative")
|
||||
return nil, errors.New("--health-start-period cannot be negative")
|
||||
}
|
||||
if copts.healthStartInterval < 0 {
|
||||
return nil, fmt.Errorf("--health-start-interval cannot be negative")
|
||||
return nil, errors.New("--health-start-interval cannot be negative")
|
||||
}
|
||||
|
||||
healthConfig = &container.HealthConfig{
|
||||
|
||||
@ -335,7 +335,7 @@ func TestParseHostname(t *testing.T) {
|
||||
hostnameWithDomain := "--hostname=hostname.domainname"
|
||||
hostnameWithDomainTld := "--hostname=hostname.domainname.tld"
|
||||
for hostname, expectedHostname := range validHostnames {
|
||||
if config, _, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname {
|
||||
if config, _, _ := mustParse(t, "--hostname="+hostname); config.Hostname != expectedHostname {
|
||||
t.Fatalf("Expected the config to have 'hostname' as %q, got %q", expectedHostname, config.Hostname)
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -15,8 +15,8 @@ func TestContainerPrunePromptTermination(t *testing.T) {
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerPruneFunc: func(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error) {
|
||||
return types.ContainersPruneReport{}, errors.New("fakeClient containerPruneFunc should not be called")
|
||||
containerPruneFunc: func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
|
||||
return container.PruneReport{}, errors.New("fakeClient containerPruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
cmd := NewPruneCommand(cli)
|
||||
|
||||
@ -2,7 +2,7 @@ package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"sort"
|
||||
"sync"
|
||||
@ -37,7 +37,7 @@ func TestRemoveForce(t *testing.T) {
|
||||
mutex.Unlock()
|
||||
|
||||
if container == "nosuchcontainer" {
|
||||
return errdefs.NotFound(fmt.Errorf("Error: no such container: " + container))
|
||||
return errdefs.NotFound(errors.New("Error: no such container: " + container))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
@ -150,7 +150,12 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
|
||||
}
|
||||
if runOpts.sigProxy {
|
||||
sigc := notifyAllSignals()
|
||||
go ForwardAllSignals(ctx, apiClient, containerID, sigc)
|
||||
// since we're explicitly setting up signal handling here, and the daemon will
|
||||
// get notified independently of the clients ctx cancellation, we use this context
|
||||
// but without cancellation to avoid ForwardAllSignals from returning
|
||||
// before all signals are forwarded.
|
||||
bgCtx := context.WithoutCancel(ctx)
|
||||
go ForwardAllSignals(bgCtx, apiClient, containerID, sigc)
|
||||
defer signal.StopCatch(sigc)
|
||||
}
|
||||
|
||||
|
||||
@ -3,13 +3,19 @@ package container
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/notary"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@ -32,6 +38,68 @@ func TestRunLabel(t *testing.T) {
|
||||
assert.NilError(t, cmd.Execute())
|
||||
}
|
||||
|
||||
func TestRunAttachTermination(t *testing.T) {
|
||||
p, tty, err := pty.Open()
|
||||
assert.NilError(t, err)
|
||||
|
||||
defer func() {
|
||||
_ = tty.Close()
|
||||
_ = p.Close()
|
||||
}()
|
||||
|
||||
killCh := make(chan struct{})
|
||||
attachCh := make(chan struct{})
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(_ *container.Config, _ *container.HostConfig, _ *network.NetworkingConfig, _ *specs.Platform, _ string) (container.CreateResponse, error) {
|
||||
return container.CreateResponse{
|
||||
ID: "id",
|
||||
}, nil
|
||||
},
|
||||
containerKillFunc: func(ctx context.Context, containerID, signal string) error {
|
||||
killCh <- struct{}{}
|
||||
return nil
|
||||
},
|
||||
containerAttachFunc: func(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
|
||||
server, client := net.Pipe()
|
||||
t.Cleanup(func() {
|
||||
_ = server.Close()
|
||||
})
|
||||
attachCh <- struct{}{}
|
||||
return types.NewHijackedResponse(client, types.MediaTypeRawStream), nil
|
||||
},
|
||||
Version: "1.36",
|
||||
}, func(fc *test.FakeCli) {
|
||||
fc.SetOut(streams.NewOut(tty))
|
||||
fc.SetIn(streams.NewIn(tty))
|
||||
})
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
assert.Equal(t, fakeCLI.In().IsTerminal(), true)
|
||||
assert.Equal(t, fakeCLI.Out().IsTerminal(), true)
|
||||
|
||||
cmd := NewRunCommand(fakeCLI)
|
||||
cmd.SetArgs([]string{"-it", "busybox"})
|
||||
cmd.SilenceUsage = true
|
||||
go func() {
|
||||
assert.ErrorIs(t, cmd.ExecuteContext(ctx), context.Canceled)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("containerAttachFunc was not called before the 5 second timeout")
|
||||
case <-attachCh:
|
||||
}
|
||||
|
||||
assert.NilError(t, syscall.Kill(syscall.Getpid(), syscall.SIGTERM))
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
cancel()
|
||||
t.Fatal("containerKillFunc was not called before the 5 second timeout")
|
||||
case <-killCh:
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@ -66,7 +134,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
||||
platform *specs.Platform,
|
||||
containerName string,
|
||||
) (container.CreateResponse, error) {
|
||||
return container.CreateResponse{}, fmt.Errorf("shouldn't try to pull image")
|
||||
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
fakeCLI.SetNotaryClient(tc.notaryFunc)
|
||||
|
||||
@ -87,7 +87,8 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
|
||||
// We always use c.ID instead of container to maintain consistency during `docker start`
|
||||
if !c.Config.Tty {
|
||||
sigc := notifyAllSignals()
|
||||
go ForwardAllSignals(ctx, dockerCli.Client(), c.ID, sigc)
|
||||
bgCtx := context.WithoutCancel(ctx)
|
||||
go ForwardAllSignals(bgCtx, dockerCli.Client(), c.ID, sigc)
|
||||
defer signal.StopCatch(sigc)
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
@ -164,7 +163,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
// is not valid for filtering containers.
|
||||
f := options.Filters.Clone()
|
||||
f.Add("type", string(events.ContainerEventType))
|
||||
eventChan, errChan := apiClient.Events(ctx, types.EventsOptions{
|
||||
eventChan, errChan := apiClient.Events(ctx, events.ListOptions{
|
||||
Filters: f,
|
||||
})
|
||||
|
||||
@ -219,7 +218,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
// with a list of container names/IDs.
|
||||
|
||||
if options.Filters != nil && options.Filters.Len() > 0 {
|
||||
return fmt.Errorf("filtering is not supported when specifying a list of containers")
|
||||
return errors.New("filtering is not supported when specifying a list of containers")
|
||||
}
|
||||
|
||||
// Create the list of containers, and start collecting stats for all
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -50,7 +50,7 @@ func (s *stats) isKnownContainer(cid string) (int, bool) {
|
||||
return -1, false
|
||||
}
|
||||
|
||||
func collect(ctx context.Context, s *Stats, cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) {
|
||||
func collect(ctx context.Context, s *Stats, cli client.ContainerAPIClient, streamStats bool, waitFirst *sync.WaitGroup) {
|
||||
logrus.Debugf("collecting stats for %s", s.Container)
|
||||
var (
|
||||
getFirst bool
|
||||
@ -78,7 +78,7 @@ func collect(ctx context.Context, s *Stats, cli client.APIClient, streamStats bo
|
||||
go func() {
|
||||
for {
|
||||
var (
|
||||
v *types.StatsJSON
|
||||
v *container.StatsResponse
|
||||
memPercent, cpuPercent float64
|
||||
blkRead, blkWrite uint64 // Only used on Linux
|
||||
mem, memLimit float64
|
||||
@ -163,7 +163,7 @@ func collect(ctx context.Context, s *Stats, cli client.APIClient, streamStats bo
|
||||
}
|
||||
}
|
||||
|
||||
func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
||||
func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *container.StatsResponse) float64 {
|
||||
var (
|
||||
cpuPercent = 0.0
|
||||
// calculate the change for the cpu usage of the container in between readings
|
||||
@ -182,7 +182,7 @@ func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJ
|
||||
return cpuPercent
|
||||
}
|
||||
|
||||
func calculateCPUPercentWindows(v *types.StatsJSON) float64 {
|
||||
func calculateCPUPercentWindows(v *container.StatsResponse) float64 {
|
||||
// Max number of 100ns intervals between the previous time read and now
|
||||
possIntervals := uint64(v.Read.Sub(v.PreRead).Nanoseconds()) // Start with number of ns intervals
|
||||
possIntervals /= 100 // Convert to number of 100ns intervals
|
||||
@ -198,7 +198,7 @@ func calculateCPUPercentWindows(v *types.StatsJSON) float64 {
|
||||
return 0.00
|
||||
}
|
||||
|
||||
func calculateBlockIO(blkio types.BlkioStats) (uint64, uint64) {
|
||||
func calculateBlockIO(blkio container.BlkioStats) (uint64, uint64) {
|
||||
var blkRead, blkWrite uint64
|
||||
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
||||
if len(bioEntry.Op) == 0 {
|
||||
@ -214,7 +214,7 @@ func calculateBlockIO(blkio types.BlkioStats) (uint64, uint64) {
|
||||
return blkRead, blkWrite
|
||||
}
|
||||
|
||||
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
|
||||
func calculateNetwork(network map[string]container.NetworkStats) (float64, float64) {
|
||||
var rx, tx float64
|
||||
|
||||
for _, v := range network {
|
||||
@ -236,7 +236,7 @@ func calculateNetwork(network map[string]types.NetworkStats) (float64, float64)
|
||||
//
|
||||
// On Docker 19.03 and older, the result was `mem.Usage - mem.Stats["cache"]`.
|
||||
// See https://github.com/moby/moby/issues/40727 for the background.
|
||||
func calculateMemUsageUnixNoCache(mem types.MemoryStats) float64 {
|
||||
func calculateMemUsageUnixNoCache(mem container.MemoryStats) float64 {
|
||||
// cgroup v1
|
||||
if v, isCgroup1 := mem.Stats["total_inactive_file"]; isCgroup1 && v < mem.Usage {
|
||||
return float64(mem.Usage - v)
|
||||
|
||||
@ -4,18 +4,12 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestCalculateMemUsageUnixNoCache(t *testing.T) {
|
||||
// Given
|
||||
stats := types.MemoryStats{Usage: 500, Stats: map[string]uint64{"total_inactive_file": 400}}
|
||||
|
||||
// When
|
||||
result := calculateMemUsageUnixNoCache(stats)
|
||||
|
||||
// Then
|
||||
result := calculateMemUsageUnixNoCache(container.MemoryStats{Usage: 500, Stats: map[string]uint64{"total_inactive_file": 400}})
|
||||
assert.Assert(t, inDelta(100.0, result, 1e-6))
|
||||
}
|
||||
|
||||
@ -36,6 +30,28 @@ func TestCalculateMemPercentUnixNoCache(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCalculateBlockIO(t *testing.T) {
|
||||
blkRead, blkWrite := calculateBlockIO(container.BlkioStats{
|
||||
IoServiceBytesRecursive: []container.BlkioStatEntry{
|
||||
{Major: 8, Minor: 0, Op: "read", Value: 1234},
|
||||
{Major: 8, Minor: 1, Op: "read", Value: 4567},
|
||||
{Major: 8, Minor: 0, Op: "Read", Value: 6},
|
||||
{Major: 8, Minor: 1, Op: "Read", Value: 8},
|
||||
{Major: 8, Minor: 0, Op: "write", Value: 123},
|
||||
{Major: 8, Minor: 1, Op: "write", Value: 456},
|
||||
{Major: 8, Minor: 0, Op: "Write", Value: 6},
|
||||
{Major: 8, Minor: 1, Op: "Write", Value: 8},
|
||||
{Major: 8, Minor: 1, Op: "", Value: 456},
|
||||
},
|
||||
})
|
||||
if blkRead != 5815 {
|
||||
t.Fatalf("blkRead = %d, want 5815", blkRead)
|
||||
}
|
||||
if blkWrite != 593 {
|
||||
t.Fatalf("blkWrite = %d, want 593", blkWrite)
|
||||
}
|
||||
}
|
||||
|
||||
func inDelta(x, y, delta float64) func() (bool, string) {
|
||||
return func() (bool, string) {
|
||||
diff := x - y
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func TestCalculateBlockIO(t *testing.T) {
|
||||
blkio := types.BlkioStats{
|
||||
IoServiceBytesRecursive: []types.BlkioStatEntry{
|
||||
{Major: 8, Minor: 0, Op: "read", Value: 1234},
|
||||
{Major: 8, Minor: 1, Op: "read", Value: 4567},
|
||||
{Major: 8, Minor: 0, Op: "Read", Value: 6},
|
||||
{Major: 8, Minor: 1, Op: "Read", Value: 8},
|
||||
{Major: 8, Minor: 0, Op: "write", Value: 123},
|
||||
{Major: 8, Minor: 1, Op: "write", Value: 456},
|
||||
{Major: 8, Minor: 0, Op: "Write", Value: 6},
|
||||
{Major: 8, Minor: 1, Op: "Write", Value: 8},
|
||||
{Major: 8, Minor: 1, Op: "", Value: 456},
|
||||
},
|
||||
}
|
||||
blkRead, blkWrite := calculateBlockIO(blkio)
|
||||
if blkRead != 5815 {
|
||||
t.Fatalf("blkRead = %d, want 5815", blkRead)
|
||||
}
|
||||
if blkWrite != 593 {
|
||||
t.Fatalf("blkWrite = %d, want 593", blkWrite)
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
@ -68,11 +67,11 @@ func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, co
|
||||
f := filters.NewArgs()
|
||||
f.Add("type", "container")
|
||||
f.Add("container", containerID)
|
||||
options := types.EventsOptions{
|
||||
Filters: f,
|
||||
}
|
||||
|
||||
eventCtx, cancel := context.WithCancel(ctx)
|
||||
eventq, errq := apiClient.Events(eventCtx, options)
|
||||
eventq, errq := apiClient.Events(eventCtx, events.ListOptions{
|
||||
Filters: f,
|
||||
})
|
||||
|
||||
eventProcessor := func(e events.Message) bool {
|
||||
stopProcessing := false
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package command
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package context
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package context
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ func writeTo(dockerCli command.Cli, reader io.Reader, dest string) error {
|
||||
var printDest bool
|
||||
if dest == "-" {
|
||||
if dockerCli.Out().IsTerminal() {
|
||||
return errors.New("cowardly refusing to export to a terminal, please specify a file path")
|
||||
return errors.New("cowardly refusing to export to a terminal, specify a file path")
|
||||
}
|
||||
writer = dockerCli.Out()
|
||||
} else {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package context
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package context
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -76,7 +75,7 @@ func validateConfig(config map[string]string, allowedKeys map[string]struct{}) e
|
||||
var errs []string
|
||||
for k := range config {
|
||||
if _, ok := allowedKeys[k]; !ok {
|
||||
errs = append(errs, fmt.Sprintf("%s: unrecognized config key", k))
|
||||
errs = append(errs, "unrecognized config key: "+k)
|
||||
}
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package command
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package command
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package command
|
||||
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// EventHandler is abstract interface for user to customize
|
||||
// own handle functions of each type of events
|
||||
//
|
||||
// Deprecated: EventHandler is no longer used, and will be removed in the next release.
|
||||
type EventHandler interface {
|
||||
Handle(action events.Action, h func(events.Message))
|
||||
Watch(c <-chan events.Message)
|
||||
}
|
||||
|
||||
// InitEventHandler initializes and returns an EventHandler
|
||||
//
|
||||
// Deprecated: InitEventHandler is no longer used, and will be removed in the next release.
|
||||
func InitEventHandler() EventHandler {
|
||||
return &eventHandler{handlers: make(map[events.Action]func(events.Message))}
|
||||
}
|
||||
|
||||
type eventHandler struct {
|
||||
handlers map[events.Action]func(events.Message)
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (w *eventHandler) Handle(action events.Action, h func(events.Message)) {
|
||||
w.mu.Lock()
|
||||
w.handlers[action] = h
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
// Watch ranges over the passed in event chan and processes the events based on the
|
||||
// handlers created for a given action.
|
||||
// To stop watching, close the event chan.
|
||||
func (w *eventHandler) Watch(c <-chan events.Message) {
|
||||
for e := range c {
|
||||
w.mu.Lock()
|
||||
h, exists := w.handlers[e.Action]
|
||||
w.mu.Unlock()
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
logrus.Debugf("event handler: received event: %v", e)
|
||||
go h(e)
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package formatter
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package formatter
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package formatter
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package formatter
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package formatter
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package formatter
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package formatter
|
||||
|
||||
|
||||
@ -106,7 +106,7 @@ type Writer struct {
|
||||
cell cell // current incomplete cell; cell.width is up to buf[pos] excluding ignored sections
|
||||
endChar byte // terminating char of escaped sequence (Escape for escapes, '>', ';' for HTML tags/entities, or 0)
|
||||
lines [][]cell // list of lines; each line is a list of cells
|
||||
widths []int // list of column widths in runes - re-used during formatting
|
||||
widths []int // list of column widths in runes - reused during formatting
|
||||
}
|
||||
|
||||
// addLine adds a new line.
|
||||
@ -115,7 +115,7 @@ type Writer struct {
|
||||
func (b *Writer) addLine(flushed bool) {
|
||||
// Grow slice instead of appending,
|
||||
// as that gives us an opportunity
|
||||
// to re-use an existing []cell.
|
||||
// to reuse an existing []cell.
|
||||
if n := len(b.lines) + 1; n <= cap(b.lines) {
|
||||
b.lines = b.lines[:n]
|
||||
b.lines[n-1] = b.lines[n-1][:0]
|
||||
@ -159,7 +159,7 @@ func (b *Writer) reset() {
|
||||
// - the sizes and widths of processed text are kept in the lines list
|
||||
// which contains a list of cells for each line
|
||||
// - the widths list is a temporary list with current widths used during
|
||||
// formatting; it is kept in Writer because it's re-used
|
||||
// formatting; it is kept in Writer because it's reused
|
||||
//
|
||||
// |<---------- size ---------->|
|
||||
// | |
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package formatter
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package idresolver
|
||||
|
||||
|
||||
@ -29,7 +29,6 @@ import (
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -81,7 +80,7 @@ func (o buildOptions) contextFromStdin() bool {
|
||||
}
|
||||
|
||||
func newBuildOptions() buildOptions {
|
||||
ulimits := make(map[string]*units.Ulimit)
|
||||
ulimits := make(map[string]*container.Ulimit)
|
||||
return buildOptions{
|
||||
tags: opts.NewListOpts(validateTag),
|
||||
buildArgs: opts.NewListOpts(opts.ValidateEnv),
|
||||
@ -458,7 +457,7 @@ func rewriteDockerfileFromForContentTrust(ctx context.Context, dockerfile io.Rea
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", reference.FamiliarString(trustedRef)))
|
||||
line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, "FROM "+reference.FamiliarString(trustedRef))
|
||||
resolvedTags = append(resolvedTags, &resolvedTag{
|
||||
digestRef: trustedRef,
|
||||
tagRef: ref,
|
||||
|
||||
@ -226,7 +226,7 @@ func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.Read
|
||||
progressOutput := streamformatter.NewProgressOutput(out)
|
||||
|
||||
// Pass the response body through a progress reader.
|
||||
progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL))
|
||||
progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", "Downloading build context from remote url: "+remoteURL)
|
||||
|
||||
return GetContextFromReader(ioutils.NewReadCloserWrapper(progReader, func() error { return response.Body.Close() }), dockerfileName)
|
||||
}
|
||||
@ -234,7 +234,7 @@ func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.Read
|
||||
// getWithStatusError does an http.Get() and returns an error if the
|
||||
// status code is 4xx or 5xx.
|
||||
func getWithStatusError(url string) (resp *http.Response, err error) {
|
||||
//#nosec G107 -- Ignore G107: Potential HTTP request made with variable url
|
||||
//nolint:gosec // Ignore G107: Potential HTTP request made with variable url
|
||||
if resp, err = http.Get(url); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ COPY data /data
|
||||
assert.DeepEqual(t, expected, fakeBuild.filenames(t))
|
||||
}
|
||||
|
||||
// TestRunBuildFromLocalGitHubDirNonExistingRepo tests that build contexts
|
||||
// TestRunBuildFromGitHubSpecialCase tests that build contexts
|
||||
// starting with `github.com/` are special-cased, and the build command attempts
|
||||
// to clone the remote repo.
|
||||
// TODO: test "context selection" logic directly when runBuild is refactored
|
||||
@ -132,7 +132,7 @@ func TestRunBuildFromGitHubSpecialCase(t *testing.T) {
|
||||
assert.ErrorContains(t, err, "docker-build-git")
|
||||
}
|
||||
|
||||
// TestRunBuildFromLocalGitHubDirNonExistingRepo tests that a local directory
|
||||
// TestRunBuildFromLocalGitHubDir tests that a local directory
|
||||
// starting with `github.com` takes precedence over the `github.com` special
|
||||
// case.
|
||||
func TestRunBuildFromLocalGitHubDir(t *testing.T) {
|
||||
|
||||
@ -21,11 +21,11 @@ type fakeClient struct {
|
||||
imagePushFunc func(ref string, options image.PushOptions) (io.ReadCloser, error)
|
||||
infoFunc func() (system.Info, error)
|
||||
imagePullFunc func(ref string, options image.PullOptions) (io.ReadCloser, error)
|
||||
imagesPruneFunc func(pruneFilter filters.Args) (types.ImagesPruneReport, error)
|
||||
imageLoadFunc func(input io.Reader, quiet bool) (types.ImageLoadResponse, error)
|
||||
imagesPruneFunc func(pruneFilter filters.Args) (image.PruneReport, error)
|
||||
imageLoadFunc func(input io.Reader, quiet bool) (image.LoadResponse, error)
|
||||
imageListFunc func(options image.ListOptions) ([]image.Summary, error)
|
||||
imageInspectFunc func(image string) (types.ImageInspect, []byte, error)
|
||||
imageImportFunc func(source types.ImageImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
|
||||
imageImportFunc func(source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
|
||||
imageHistoryFunc func(image string) ([]image.HistoryResponseItem, error)
|
||||
imageBuildFunc func(context.Context, io.Reader, types.ImageBuildOptions) (types.ImageBuildResponse, error)
|
||||
}
|
||||
@ -74,18 +74,18 @@ func (cli *fakeClient) ImagePull(_ context.Context, ref string, options image.Pu
|
||||
return io.NopCloser(strings.NewReader("")), nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ImagesPrune(_ context.Context, pruneFilter filters.Args) (types.ImagesPruneReport, error) {
|
||||
func (cli *fakeClient) ImagesPrune(_ context.Context, pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
if cli.imagesPruneFunc != nil {
|
||||
return cli.imagesPruneFunc(pruneFilter)
|
||||
}
|
||||
return types.ImagesPruneReport{}, nil
|
||||
return image.PruneReport{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ImageLoad(_ context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
|
||||
func (cli *fakeClient) ImageLoad(_ context.Context, input io.Reader, quiet bool) (image.LoadResponse, error) {
|
||||
if cli.imageLoadFunc != nil {
|
||||
return cli.imageLoadFunc(input, quiet)
|
||||
}
|
||||
return types.ImageLoadResponse{}, nil
|
||||
return image.LoadResponse{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ImageList(_ context.Context, options image.ListOptions) ([]image.Summary, error) {
|
||||
@ -102,7 +102,7 @@ func (cli *fakeClient) ImageInspectWithRaw(_ context.Context, img string) (types
|
||||
return types.ImageInspect{}, nil, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ImageImport(_ context.Context, source types.ImageImportSource, ref string,
|
||||
func (cli *fakeClient) ImageImport(_ context.Context, source image.ImportSource, ref string,
|
||||
options image.ImportOptions,
|
||||
) (io.ReadCloser, error) {
|
||||
if cli.imageImportFunc != nil {
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
dockeropts "github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/spf13/cobra"
|
||||
@ -53,17 +52,17 @@ func NewImportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
func runImport(ctx context.Context, dockerCli command.Cli, options importOptions) error {
|
||||
var source types.ImageImportSource
|
||||
var source image.ImportSource
|
||||
switch {
|
||||
case options.source == "-":
|
||||
// import from STDIN
|
||||
source = types.ImageImportSource{
|
||||
source = image.ImportSource{
|
||||
Source: dockerCli.In(),
|
||||
SourceName: options.source,
|
||||
}
|
||||
case strings.HasPrefix(options.source, "https://"), strings.HasPrefix(options.source, "http://"):
|
||||
// import from a remote source (handled by the daemon)
|
||||
source = types.ImageImportSource{
|
||||
source = image.ImportSource{
|
||||
SourceName: options.source,
|
||||
}
|
||||
default:
|
||||
@ -73,7 +72,7 @@ func runImport(ctx context.Context, dockerCli command.Cli, options importOptions
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
source = types.ImageImportSource{
|
||||
source = image.ImportSource{
|
||||
Source: file,
|
||||
SourceName: "-",
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
@ -18,7 +17,7 @@ func TestNewImportCommandErrors(t *testing.T) {
|
||||
name string
|
||||
args []string
|
||||
expectedError string
|
||||
imageImportFunc func(source types.ImageImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
|
||||
imageImportFunc func(source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
|
||||
}{
|
||||
{
|
||||
name: "wrong-args",
|
||||
@ -29,7 +28,7 @@ func TestNewImportCommandErrors(t *testing.T) {
|
||||
name: "import-failed",
|
||||
args: []string{"testdata/import-command-success.input.txt"},
|
||||
expectedError: "something went wrong",
|
||||
imageImportFunc: func(source types.ImageImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
|
||||
imageImportFunc: func(source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
|
||||
return nil, errors.Errorf("something went wrong")
|
||||
},
|
||||
},
|
||||
@ -53,7 +52,7 @@ func TestNewImportCommandSuccess(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
imageImportFunc func(source types.ImageImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
|
||||
imageImportFunc func(source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
@ -66,7 +65,7 @@ func TestNewImportCommandSuccess(t *testing.T) {
|
||||
{
|
||||
name: "double",
|
||||
args: []string{"-", "image:local"},
|
||||
imageImportFunc: func(source types.ImageImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
|
||||
imageImportFunc: func(source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
|
||||
assert.Check(t, is.Equal("image:local", ref))
|
||||
return io.NopCloser(strings.NewReader("")), nil
|
||||
},
|
||||
@ -74,7 +73,7 @@ func TestNewImportCommandSuccess(t *testing.T) {
|
||||
{
|
||||
name: "message",
|
||||
args: []string{"--message", "test message", "-"},
|
||||
imageImportFunc: func(source types.ImageImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
|
||||
imageImportFunc: func(source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
|
||||
assert.Check(t, is.Equal("test message", options.Message))
|
||||
return io.NopCloser(strings.NewReader("")), nil
|
||||
},
|
||||
@ -82,7 +81,7 @@ func TestNewImportCommandSuccess(t *testing.T) {
|
||||
{
|
||||
name: "change",
|
||||
args: []string{"--change", "ENV DEBUG=true", "-"},
|
||||
imageImportFunc: func(source types.ImageImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
|
||||
imageImportFunc: func(source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
|
||||
assert.Check(t, is.Equal("ENV DEBUG=true", options.Changes[0]))
|
||||
return io.NopCloser(strings.NewReader("")), nil
|
||||
},
|
||||
@ -90,7 +89,7 @@ func TestNewImportCommandSuccess(t *testing.T) {
|
||||
{
|
||||
name: "change legacy syntax",
|
||||
args: []string{"--change", "ENV DEBUG true", "-"},
|
||||
imageImportFunc: func(source types.ImageImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
|
||||
imageImportFunc: func(source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
|
||||
assert.Check(t, is.Equal("ENV DEBUG true", options.Changes[0]))
|
||||
return io.NopCloser(strings.NewReader("")), nil
|
||||
},
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package image
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
@ -19,7 +19,7 @@ func TestNewLoadCommandErrors(t *testing.T) {
|
||||
args []string
|
||||
isTerminalIn bool
|
||||
expectedError string
|
||||
imageLoadFunc func(input io.Reader, quiet bool) (types.ImageLoadResponse, error)
|
||||
imageLoadFunc func(input io.Reader, quiet bool) (image.LoadResponse, error)
|
||||
}{
|
||||
{
|
||||
name: "wrong-args",
|
||||
@ -34,8 +34,8 @@ func TestNewLoadCommandErrors(t *testing.T) {
|
||||
{
|
||||
name: "pull-error",
|
||||
expectedError: "something went wrong",
|
||||
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
|
||||
return types.ImageLoadResponse{}, errors.Errorf("something went wrong")
|
||||
imageLoadFunc: func(input io.Reader, quiet bool) (image.LoadResponse, error) {
|
||||
return image.LoadResponse{}, errors.Errorf("something went wrong")
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -62,19 +62,19 @@ func TestNewLoadCommandSuccess(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
imageLoadFunc func(input io.Reader, quiet bool) (types.ImageLoadResponse, error)
|
||||
imageLoadFunc func(input io.Reader, quiet bool) (image.LoadResponse, error)
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
|
||||
return types.ImageLoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
|
||||
imageLoadFunc: func(input io.Reader, quiet bool) (image.LoadResponse, error) {
|
||||
return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "json",
|
||||
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
|
||||
imageLoadFunc: func(input io.Reader, quiet bool) (image.LoadResponse, error) {
|
||||
json := "{\"ID\": \"1\"}"
|
||||
return types.ImageLoadResponse{
|
||||
return image.LoadResponse{
|
||||
Body: io.NopCloser(strings.NewReader(json)),
|
||||
JSON: true,
|
||||
}, nil
|
||||
@ -83,8 +83,8 @@ func TestNewLoadCommandSuccess(t *testing.T) {
|
||||
{
|
||||
name: "input-file",
|
||||
args: []string{"--input", "testdata/load-command-success.input.txt"},
|
||||
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
|
||||
return types.ImageLoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
|
||||
imageLoadFunc: func(input io.Reader, quiet bool) (image.LoadResponse, error) {
|
||||
return image.LoadResponse{Body: io.NopCloser(strings.NewReader("Success"))}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/pkg/errors"
|
||||
@ -23,7 +22,7 @@ func TestNewPruneCommandErrors(t *testing.T) {
|
||||
name string
|
||||
args []string
|
||||
expectedError string
|
||||
imagesPruneFunc func(pruneFilter filters.Args) (types.ImagesPruneReport, error)
|
||||
imagesPruneFunc func(pruneFilter filters.Args) (image.PruneReport, error)
|
||||
}{
|
||||
{
|
||||
name: "wrong-args",
|
||||
@ -34,8 +33,8 @@ func TestNewPruneCommandErrors(t *testing.T) {
|
||||
name: "prune-error",
|
||||
args: []string{"--force"},
|
||||
expectedError: "something went wrong",
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
|
||||
return types.ImagesPruneReport{}, errors.Errorf("something went wrong")
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
return image.PruneReport{}, errors.Errorf("something went wrong")
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -53,22 +52,22 @@ func TestNewPruneCommandSuccess(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
imagesPruneFunc func(pruneFilter filters.Args) (types.ImagesPruneReport, error)
|
||||
imagesPruneFunc func(pruneFilter filters.Args) (image.PruneReport, error)
|
||||
}{
|
||||
{
|
||||
name: "all",
|
||||
args: []string{"--all"},
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
assert.Check(t, is.Equal("false", pruneFilter.Get("dangling")[0]))
|
||||
return types.ImagesPruneReport{}, nil
|
||||
return image.PruneReport{}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "force-deleted",
|
||||
args: []string{"--force"},
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
assert.Check(t, is.Equal("true", pruneFilter.Get("dangling")[0]))
|
||||
return types.ImagesPruneReport{
|
||||
return image.PruneReport{
|
||||
ImagesDeleted: []image.DeleteResponse{{Deleted: "image1"}},
|
||||
SpaceReclaimed: 1,
|
||||
}, nil
|
||||
@ -77,17 +76,17 @@ func TestNewPruneCommandSuccess(t *testing.T) {
|
||||
{
|
||||
name: "label-filter",
|
||||
args: []string{"--force", "--filter", "label=foobar"},
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
assert.Check(t, is.Equal("foobar", pruneFilter.Get("label")[0]))
|
||||
return types.ImagesPruneReport{}, nil
|
||||
return image.PruneReport{}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "force-untagged",
|
||||
args: []string{"--force"},
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
assert.Check(t, is.Equal("true", pruneFilter.Get("dangling")[0]))
|
||||
return types.ImagesPruneReport{
|
||||
return image.PruneReport{
|
||||
ImagesDeleted: []image.DeleteResponse{{Untagged: "image1"}},
|
||||
SpaceReclaimed: 2,
|
||||
}, nil
|
||||
@ -115,8 +114,8 @@ func TestPrunePromptTermination(t *testing.T) {
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
|
||||
return types.ImagesPruneReport{}, errors.New("fakeClient imagesPruneFunc should not be called")
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (image.PruneReport, error) {
|
||||
return image.PruneReport{}, errors.New("fakeClient imagesPruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
cmd := NewPruneCommand(cli)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
@ -112,7 +113,7 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader("")), fmt.Errorf("shouldn't try to pull image")
|
||||
return io.NopCloser(strings.NewReader("")), errors.New("shouldn't try to pull image")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
cli.SetNotaryClient(tc.notaryFunc)
|
||||
|
||||
@ -1,19 +1,28 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/docker/api/types/auxprogress"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/morikuni/aec"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -23,6 +32,7 @@ type pushOptions struct {
|
||||
remote string
|
||||
untrusted bool
|
||||
quiet bool
|
||||
platform string
|
||||
}
|
||||
|
||||
// NewPushCommand creates a new `docker push` command
|
||||
@ -48,12 +58,33 @@ func NewPushCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Push all tags of an image to the repository")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output")
|
||||
command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled())
|
||||
flags.StringVar(&opts.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"),
|
||||
`Push a platform-specific manifest as a single-platform image to the registry.
|
||||
'os[/arch[/variant]]': Explicit platform (eg. linux/amd64)`)
|
||||
flags.SetAnnotation("platform", "version", []string{"1.46"})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunPush performs a push against the engine based on the specified options
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error {
|
||||
var platform *ocispec.Platform
|
||||
if opts.platform != "" {
|
||||
p, err := platforms.Parse(opts.platform)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "Invalid platform %s", opts.platform)
|
||||
return err
|
||||
}
|
||||
platform = &p
|
||||
|
||||
printNote(dockerCli, `Selecting a single platform will only push one matching image manifest from a multi-platform image index.
|
||||
This means that any other components attached to the multi-platform image index (like Buildkit attestations) won't be pushed.
|
||||
If you want to only push a single platform image while preserving the attestations, please use 'docker convert\n'
|
||||
`)
|
||||
}
|
||||
|
||||
ref, err := reference.ParseNormalizedNamed(opts.remote)
|
||||
switch {
|
||||
case err != nil:
|
||||
@ -84,6 +115,7 @@ func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error
|
||||
All: opts.all,
|
||||
RegistryAuth: encodedAuth,
|
||||
PrivilegeFunc: requestPrivilege,
|
||||
Platform: platform,
|
||||
}
|
||||
|
||||
responseBody, err := dockerCli.Client().ImagePush(ctx, reference.FamiliarString(ref), options)
|
||||
@ -91,6 +123,13 @@ func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for _, note := range notes {
|
||||
fmt.Fprintln(dockerCli.Err(), "")
|
||||
printNote(dockerCli, note)
|
||||
}
|
||||
}()
|
||||
|
||||
defer responseBody.Close()
|
||||
if !opts.untrusted {
|
||||
// TODO PushTrustedReference currently doesn't respect `--quiet`
|
||||
@ -98,11 +137,51 @@ func RunPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error
|
||||
}
|
||||
|
||||
if opts.quiet {
|
||||
err = jsonmessage.DisplayJSONMessagesToStream(responseBody, streams.NewOut(io.Discard), nil)
|
||||
err = jsonmessage.DisplayJSONMessagesToStream(responseBody, streams.NewOut(io.Discard), handleAux(dockerCli))
|
||||
if err == nil {
|
||||
fmt.Fprintln(dockerCli.Out(), ref.String())
|
||||
}
|
||||
return err
|
||||
}
|
||||
return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
|
||||
return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), handleAux(dockerCli))
|
||||
}
|
||||
|
||||
var notes []string
|
||||
|
||||
func handleAux(dockerCli command.Cli) func(jm jsonmessage.JSONMessage) {
|
||||
return func(jm jsonmessage.JSONMessage) {
|
||||
b := []byte(*jm.Aux)
|
||||
|
||||
var stripped auxprogress.ManifestPushedInsteadOfIndex
|
||||
err := json.Unmarshal(b, &stripped)
|
||||
if err == nil && stripped.ManifestPushedInsteadOfIndex {
|
||||
note := fmt.Sprintf("Not all multiplatform-content is present and only the available single-platform image was pushed\n%s -> %s",
|
||||
aec.RedF.Apply(stripped.OriginalIndex.Digest.String()),
|
||||
aec.GreenF.Apply(stripped.SelectedManifest.Digest.String()),
|
||||
)
|
||||
notes = append(notes, note)
|
||||
}
|
||||
|
||||
var missing auxprogress.ContentMissing
|
||||
err = json.Unmarshal(b, &missing)
|
||||
if err == nil && missing.ContentMissing {
|
||||
note := `You're trying to push a manifest list/index which
|
||||
references multiple platform specific manifests, but not all of them are available locally
|
||||
or available to the remote repository.
|
||||
|
||||
Make sure you have all the referenced content and try again.
|
||||
|
||||
You can also push only a single platform specific manifest directly by specifying the platform you want to push with the --platform flag.`
|
||||
notes = append(notes, note)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printNote(dockerCli command.Cli, format string, args ...any) {
|
||||
if dockerCli.Err().IsTerminal() {
|
||||
_, _ = fmt.Fprint(dockerCli.Err(), aec.WhiteF.Apply(aec.CyanB.Apply("[ NOTE ]"))+" ")
|
||||
} else {
|
||||
_, _ = fmt.Fprint(dockerCli.Err(), "[ NOTE ] ")
|
||||
}
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), aec.Bold.Apply(format)+"\n", args...)
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ type notFound struct {
|
||||
}
|
||||
|
||||
func (n notFound) Error() string {
|
||||
return fmt.Sprintf("Error: No such image: %s", n.imageID)
|
||||
return "Error: No such image: " + n.imageID
|
||||
}
|
||||
|
||||
func (n notFound) NotFound() {}
|
||||
|
||||
@ -100,7 +100,7 @@ func PushTrustedReference(ioStreams command.Streams, repoInfo *registry.Reposito
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return errors.Errorf("no targets found, please provide a specific tag in order to sign it")
|
||||
return errors.Errorf("no targets found, provide a specific tag in order to sign it")
|
||||
}
|
||||
|
||||
fmt.Fprintln(ioStreams.Out(), "Signing and pushing trust metadata")
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.19
|
||||
//go:build go1.21
|
||||
|
||||
package inspect
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ package network
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
@ -11,20 +10,20 @@ import (
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
|
||||
networkCreateFunc func(ctx context.Context, name string, options network.CreateOptions) (network.CreateResponse, error)
|
||||
networkConnectFunc func(ctx context.Context, networkID, container string, config *network.EndpointSettings) error
|
||||
networkDisconnectFunc func(ctx context.Context, networkID, container string, force bool) error
|
||||
networkRemoveFunc func(ctx context.Context, networkID string) error
|
||||
networkListFunc func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
|
||||
networkPruneFunc func(ctx context.Context, pruneFilters filters.Args) (types.NetworksPruneReport, error)
|
||||
networkInspectFunc func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error)
|
||||
networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error)
|
||||
networkPruneFunc func(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error)
|
||||
networkInspectFunc func(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, []byte, error)
|
||||
}
|
||||
|
||||
func (c *fakeClient) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) {
|
||||
func (c *fakeClient) NetworkCreate(ctx context.Context, name string, options network.CreateOptions) (network.CreateResponse, error) {
|
||||
if c.networkCreateFunc != nil {
|
||||
return c.networkCreateFunc(ctx, name, options)
|
||||
}
|
||||
return types.NetworkCreateResponse{}, nil
|
||||
return network.CreateResponse{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error {
|
||||
@ -41,11 +40,11 @@ func (c *fakeClient) NetworkDisconnect(ctx context.Context, networkID, container
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
|
||||
func (c *fakeClient) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
|
||||
if c.networkListFunc != nil {
|
||||
return c.networkListFunc(ctx, options)
|
||||
}
|
||||
return []types.NetworkResource{}, nil
|
||||
return []network.Inspect{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) NetworkRemove(ctx context.Context, networkID string) error {
|
||||
@ -55,16 +54,16 @@ func (c *fakeClient) NetworkRemove(ctx context.Context, networkID string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) NetworkInspectWithRaw(ctx context.Context, networkID string, opts types.NetworkInspectOptions) (types.NetworkResource, []byte, error) {
|
||||
func (c *fakeClient) NetworkInspectWithRaw(ctx context.Context, networkID string, opts network.InspectOptions) (network.Inspect, []byte, error) {
|
||||
if c.networkInspectFunc != nil {
|
||||
return c.networkInspectFunc(ctx, networkID, opts)
|
||||
}
|
||||
return types.NetworkResource{}, nil, nil
|
||||
return network.Inspect{}, nil, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error) {
|
||||
func (c *fakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error) {
|
||||
if c.networkPruneFunc != nil {
|
||||
return c.networkPruneFunc(ctx, pruneFilter)
|
||||
}
|
||||
return types.NetworksPruneReport{}, nil
|
||||
return network.PruneReport{}, nil
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -36,14 +37,14 @@ func newConnectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.network = args[0]
|
||||
options.container = args[1]
|
||||
return runConnect(cmd.Context(), dockerCli, options)
|
||||
return runConnect(cmd.Context(), dockerCli.Client(), options)
|
||||
},
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) == 0 {
|
||||
return completion.NetworkNames(dockerCli)(cmd, args, toComplete)
|
||||
}
|
||||
network := args[0]
|
||||
return completion.ContainerNames(dockerCli, true, not(isConnected(network)))(cmd, args, toComplete)
|
||||
nw := args[0]
|
||||
return completion.ContainerNames(dockerCli, true, not(isConnected(nw)))(cmd, args, toComplete)
|
||||
},
|
||||
}
|
||||
|
||||
@ -57,14 +58,13 @@ func newConnectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runConnect(ctx context.Context, dockerCli command.Cli, options connectOptions) error {
|
||||
client := dockerCli.Client()
|
||||
|
||||
func runConnect(ctx context.Context, apiClient client.NetworkAPIClient, options connectOptions) error {
|
||||
driverOpts, err := convertDriverOpt(options.driverOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
epConfig := &network.EndpointSettings{
|
||||
|
||||
return apiClient.NetworkConnect(ctx, options.network, options.container, &network.EndpointSettings{
|
||||
IPAMConfig: &network.EndpointIPAMConfig{
|
||||
IPv4Address: options.ipaddress,
|
||||
IPv6Address: options.ipv6address,
|
||||
@ -73,9 +73,7 @@ func runConnect(ctx context.Context, dockerCli command.Cli, options connectOptio
|
||||
Links: options.links.GetAll(),
|
||||
Aliases: options.aliases,
|
||||
DriverOpts: driverOpts,
|
||||
}
|
||||
|
||||
return client.NetworkConnect(ctx, options.network, options.container, epConfig)
|
||||
})
|
||||
}
|
||||
|
||||
func convertDriverOpt(options []string) (map[string]string, error) {
|
||||
@ -85,7 +83,7 @@ func convertDriverOpt(options []string) (map[string]string, error) {
|
||||
// TODO(thaJeztah): we should probably not accept whitespace here (both for key and value).
|
||||
k = strings.TrimSpace(k)
|
||||
if !ok || k == "" {
|
||||
return nil, fmt.Errorf("invalid key/value pair format in driver options")
|
||||
return nil, errors.New("invalid key/value pair format in driver options")
|
||||
}
|
||||
driverOpt[k] = strings.TrimSpace(v)
|
||||
}
|
||||
|
||||
@ -43,27 +43,41 @@ func TestNetworkConnectErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNetworkConnectWithFlags(t *testing.T) {
|
||||
expectedOpts := []network.IPAMConfig{
|
||||
{
|
||||
Subnet: "192.168.4.0/24",
|
||||
IPRange: "192.168.4.0/24",
|
||||
Gateway: "192.168.4.1/24",
|
||||
AuxAddress: map[string]string{},
|
||||
expectedConfig := &network.EndpointSettings{
|
||||
IPAMConfig: &network.EndpointIPAMConfig{
|
||||
IPv4Address: "192.168.4.1",
|
||||
IPv6Address: "fdef:f401:8da0:1234::5678",
|
||||
LinkLocalIPs: []string{"169.254.42.42"},
|
||||
},
|
||||
Links: []string{"otherctr"},
|
||||
Aliases: []string{"poor-yorick"},
|
||||
DriverOpts: map[string]string{
|
||||
"driveropt1": "optval1,optval2",
|
||||
"driveropt2": "optval4",
|
||||
},
|
||||
}
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
networkConnectFunc: func(ctx context.Context, networkID, container string, config *network.EndpointSettings) error {
|
||||
assert.Check(t, is.DeepEqual(expectedOpts, config.IPAMConfig), "not expected driver error")
|
||||
assert.Check(t, is.DeepEqual(expectedConfig, config))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
args := []string{"banana"}
|
||||
cmd := newCreateCommand(cli)
|
||||
args := []string{"mynet", "myctr"}
|
||||
cmd := newConnectCommand(cli)
|
||||
|
||||
cmd.SetArgs(args)
|
||||
cmd.Flags().Set("driver", "foo")
|
||||
cmd.Flags().Set("ip-range", "192.168.4.0/24")
|
||||
cmd.Flags().Set("gateway", "192.168.4.1/24")
|
||||
cmd.Flags().Set("subnet", "192.168.4.0/24")
|
||||
for _, opt := range []struct{ name, value string }{
|
||||
{"alias", "poor-yorick"},
|
||||
{"driver-opt", "\"driveropt1=optval1,optval2\""},
|
||||
{"driver-opt", "driveropt2=optval3"},
|
||||
{"driver-opt", "driveropt2=optval4"}, // replaces value
|
||||
{"ip", "192.168.4.1"},
|
||||
{"ip6", "fdef:f401:8da0:1234::5678"},
|
||||
{"link", "otherctr"},
|
||||
{"link-local-ip", "169.254.42.42"},
|
||||
} {
|
||||
err := cmd.Flags().Set(opt.name, opt.value)
|
||||
assert.Check(t, err)
|
||||
}
|
||||
assert.NilError(t, cmd.Execute())
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@ -23,7 +22,7 @@ type createOptions struct {
|
||||
driverOpts opts.MapOpts
|
||||
labels opts.ListOpts
|
||||
internal bool
|
||||
ipv6 bool
|
||||
ipv6 *bool
|
||||
attachable bool
|
||||
ingress bool
|
||||
configOnly bool
|
||||
@ -38,6 +37,7 @@ type createOptions struct {
|
||||
}
|
||||
|
||||
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var ipv6 bool
|
||||
options := createOptions{
|
||||
driverOpts: *opts.NewMapOpts(nil, nil),
|
||||
labels: opts.NewListOpts(opts.ValidateLabel),
|
||||
@ -51,6 +51,11 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.name = args[0]
|
||||
|
||||
if cmd.Flag("ipv6").Changed {
|
||||
options.ipv6 = &ipv6
|
||||
}
|
||||
|
||||
return runCreate(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
ValidArgsFunction: completion.NoComplete,
|
||||
@ -61,7 +66,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options")
|
||||
flags.Var(&options.labels, "label", "Set metadata on a network")
|
||||
flags.BoolVar(&options.internal, "internal", false, "Restrict external access to the network")
|
||||
flags.BoolVar(&options.ipv6, "ipv6", false, "Enable IPv6 networking")
|
||||
flags.BoolVar(&ipv6, "ipv6", false, "Enable or disable IPv6 networking")
|
||||
flags.BoolVar(&options.attachable, "attachable", false, "Enable manual container attachment")
|
||||
flags.SetAnnotation("attachable", "version", []string{"1.25"})
|
||||
flags.BoolVar(&options.ingress, "ingress", false, "Create swarm routing-mesh network")
|
||||
@ -98,7 +103,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, options createOptions
|
||||
Network: options.configFrom,
|
||||
}
|
||||
}
|
||||
resp, err := client.NetworkCreate(ctx, options.name, types.NetworkCreate{
|
||||
resp, err := client.NetworkCreate(ctx, options.name, network.CreateOptions{
|
||||
Driver: options.driver,
|
||||
Options: options.driverOpts.GetAll(),
|
||||
IPAM: &network.IPAM{
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
@ -18,7 +17,7 @@ func TestNetworkCreateErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
|
||||
networkCreateFunc func(ctx context.Context, name string, options network.CreateOptions) (network.CreateResponse, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
@ -26,8 +25,8 @@ func TestNetworkCreateErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
args: []string{"toto"},
|
||||
networkCreateFunc: func(ctx context.Context, name string, createBody types.NetworkCreate) (types.NetworkCreateResponse, error) {
|
||||
return types.NetworkCreateResponse{}, errors.Errorf("error creating network")
|
||||
networkCreateFunc: func(ctx context.Context, name string, createBody network.CreateOptions) (network.CreateResponse, error) {
|
||||
return network.CreateResponse{}, errors.Errorf("error creating network")
|
||||
},
|
||||
expectedError: "error creating network",
|
||||
},
|
||||
@ -153,10 +152,10 @@ func TestNetworkCreateWithFlags(t *testing.T) {
|
||||
},
|
||||
}
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
networkCreateFunc: func(ctx context.Context, name string, createBody types.NetworkCreate) (types.NetworkCreateResponse, error) {
|
||||
assert.Check(t, is.Equal(expectedDriver, createBody.Driver), "not expected driver error")
|
||||
assert.Check(t, is.DeepEqual(expectedOpts, createBody.IPAM.Config), "not expected driver error")
|
||||
return types.NetworkCreateResponse{
|
||||
networkCreateFunc: func(ctx context.Context, name string, options network.CreateOptions) (network.CreateResponse, error) {
|
||||
assert.Check(t, is.Equal(expectedDriver, options.Driver), "not expected driver error")
|
||||
assert.Check(t, is.DeepEqual(expectedOpts, options.IPAM.Config), "not expected driver error")
|
||||
return network.CreateResponse{
|
||||
ID: name,
|
||||
}, nil
|
||||
},
|
||||
@ -172,3 +171,58 @@ func TestNetworkCreateWithFlags(t *testing.T) {
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Check(t, is.Equal("banana", strings.TrimSpace(cli.OutBuffer().String())))
|
||||
}
|
||||
|
||||
// TestNetworkCreateIPv6 verifies behavior of the "--ipv6" option. This option
|
||||
// is an optional bool, and must default to "nil", not "true" or "false".
|
||||
func TestNetworkCreateIPv6(t *testing.T) {
|
||||
strPtr := func(val bool) *bool { return &val }
|
||||
|
||||
tests := []struct {
|
||||
doc, name string
|
||||
flags []string
|
||||
expected *bool
|
||||
}{
|
||||
{
|
||||
doc: "IPV6 default",
|
||||
name: "ipv6-default",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
doc: "IPV6 enabled",
|
||||
name: "ipv6-enabled",
|
||||
flags: []string{"--ipv6=true"},
|
||||
expected: strPtr(true),
|
||||
},
|
||||
{
|
||||
doc: "IPV6 enabled (shorthand)",
|
||||
name: "ipv6-enabled-shorthand",
|
||||
flags: []string{"--ipv6"},
|
||||
expected: strPtr(true),
|
||||
},
|
||||
{
|
||||
doc: "IPV6 disabled",
|
||||
name: "ipv6-disabled",
|
||||
flags: []string{"--ipv6=false"},
|
||||
expected: strPtr(false),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
networkCreateFunc: func(ctx context.Context, name string, createBody network.CreateOptions) (network.CreateResponse, error) {
|
||||
assert.Check(t, is.DeepEqual(tc.expected, createBody.EnableIPv6))
|
||||
return network.CreateResponse{ID: name}, nil
|
||||
},
|
||||
})
|
||||
cmd := newCreateCommand(cli)
|
||||
cmd.SetArgs([]string{tc.name})
|
||||
if tc.expected != nil {
|
||||
assert.Check(t, cmd.ParseFlags(tc.flags))
|
||||
}
|
||||
assert.NilError(t, cmd.Execute())
|
||||
assert.Check(t, is.Equal(tc.name, strings.TrimSpace(cli.OutBuffer().String())))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
)
|
||||
|
||||
@ -35,10 +35,10 @@ func NewFormat(source string, quiet bool) formatter.Format {
|
||||
}
|
||||
|
||||
// FormatWrite writes the context
|
||||
func FormatWrite(ctx formatter.Context, networks []types.NetworkResource) error {
|
||||
func FormatWrite(ctx formatter.Context, networks []network.Summary) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, network := range networks {
|
||||
networkCtx := &networkContext{trunc: ctx.Trunc, n: network}
|
||||
for _, nw := range networks {
|
||||
networkCtx := &networkContext{trunc: ctx.Trunc, n: nw}
|
||||
if err := format(networkCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -62,7 +62,7 @@ func FormatWrite(ctx formatter.Context, networks []types.NetworkResource) error
|
||||
type networkContext struct {
|
||||
formatter.HeaderContext
|
||||
trunc bool
|
||||
n types.NetworkResource
|
||||
n network.Summary
|
||||
}
|
||||
|
||||
func (c *networkContext) MarshalJSON() ([]byte, error) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user